blob: b13d69966fb2dd431b643f91f84a2ef618669af0 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.geronimo.arthur.knight.openjpa;
import org.apache.geronimo.arthur.spi.ArthurExtension;
import org.apache.geronimo.arthur.spi.model.ClassReflectionModel;
import org.apache.geronimo.arthur.spi.model.ResourceBundleModel;
import org.apache.geronimo.arthur.spi.model.ResourceModel;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.MappedSuperclass;
import java.lang.reflect.Field;
import java.util.Objects;
import java.util.stream.Stream;
public class OpenJPAExtension implements ArthurExtension {
@Override
public void execute(final Context context) {
registerJPAClasses(context);
registerSPI(context);
registerI18n(context);
registerDictionaryResources(context);
registerPrimitiveWrappers(context);
registerDBCP2IfPresent(context);
}
// see Options class (stringToObject method)
private void registerPrimitiveWrappers(final Context context) {
Stream.of(Boolean.class, Byte.class, Character.class, Double.class, Float.class, Integer.class, Long.class, Short.class)
.forEach(it -> context.register(new ClassReflectionModel(it.getName(), null, true, null, true /*valueOf*/, null, null, null, null, null, null)));
}
private void registerDictionaryResources(final Context context) {
context.register(new ResourceModel("org\\/apache\\/openjpa\\/jdbc\\/sql\\/sql-keywords\\.rsrc"));
context.register(new ResourceModel("org\\/apache\\/openjpa\\/jdbc\\/sql\\/sql-error-state-codes\\.xml"));
}
// todo: dbcp2-knight and inherit from it automatically?
private void registerDBCP2IfPresent(final Context context) {
try {
final Class<?> evictionPolicy = context.loadClass("org.apache.commons.pool2.impl.DefaultEvictionPolicy");
final ClassReflectionModel model = new ClassReflectionModel();
model.setName(evictionPolicy.getName());
model.setAllPublicConstructors(true);
context.register(model);
context.register(new ResourceBundleModel("org.apache.commons.dbcp2.LocalStrings"));
// dbcp2 depends on commons-logging in a hardcoded way (don't ask)
addCommonsLogging(context).forEach(it -> {
final ClassReflectionModel reflect = new ClassReflectionModel();
reflect.setName(it.getName());
reflect.setAllPublicConstructors(true);
reflect.setAllDeclaredConstructors(true);
context.register(reflect);
});
} catch (final NoClassDefFoundError | Exception e) {
// no-op
}
}
// todo: replace that by a ResourceFinder?
// does not move often so maybe overkill but pattern being stable
// (org/apache/openjpa/*/localizer.properties) we could
private void registerI18n(final Context context) {
final ClassLoader loader = Thread.currentThread().getContextClassLoader();
Stream.of(
"org/apache/openjpa/abstractstore/localizer.properties",
"org/apache/openjpa/ant/localizer.properties",
"org/apache/openjpa/conf/localizer.properties",
"org/apache/openjpa/datacache/localizer.properties",
"org/apache/openjpa/ee/localizer.properties",
"org/apache/openjpa/enhance/localizer.properties",
"org/apache/openjpa/enhance/stats/localizer.properties",
"org/apache/openjpa/event/kubernetes/localizer.properties",
"org/apache/openjpa/event/localizer.properties",
"org/apache/openjpa/instrumentation/jmx/localizer.properties",
"org/apache/openjpa/jdbc/ant/localizer.properties",
"org/apache/openjpa/jdbc/conf/localizer.properties",
"org/apache/openjpa/jdbc/kernel/exps/localizer.properties",
"org/apache/openjpa/jdbc/kernel/localizer.properties",
"org/apache/openjpa/jdbc/meta/localizer.properties",
"org/apache/openjpa/jdbc/meta/strats/localizer.properties",
"org/apache/openjpa/jdbc/schema/localizer.properties",
"org/apache/openjpa/jdbc/sql/localizer.properties",
"org/apache/openjpa/kernel/exps/localizer.properties",
"org/apache/openjpa/kernel/jpql/localizer.properties",
"org/apache/openjpa/kernel/localizer.properties",
"org/apache/openjpa/lib/ant/localizer.properties",
"org/apache/openjpa/lib/conf/localizer.properties",
"org/apache/openjpa/lib/graph/localizer.properties",
"org/apache/openjpa/lib/jdbc/localizer.properties",
"org/apache/openjpa/lib/log/localizer.properties",
"org/apache/openjpa/lib/meta/localizer.properties",
"org/apache/openjpa/lib/rop/localizer.properties",
"org/apache/openjpa/lib/util/localizer.properties",
"org/apache/openjpa/lib/xml/localizer.properties",
"org/apache/openjpa/meta/localizer.properties",
"org/apache/openjpa/persistence/criteria/localizer.properties",
"org/apache/openjpa/persistence/jdbc/localizer.properties",
"org/apache/openjpa/persistence/jest/localizer.properties",
"org/apache/openjpa/persistence/localizer.properties",
"org/apache/openjpa/persistence/meta/localizer.properties",
"org/apache/openjpa/persistence/util/localizer.properties",
"org/apache/openjpa/persistence/validation/localizer.properties",
"org/apache/openjpa/slice/jdbc/localizer.properties",
"org/apache/openjpa/slice/localizer.properties",
"org/apache/openjpa/slice/transaction/localizer.properties",
"org/apache/openjpa/util/localizer.properties"
).filter(it -> loader.getResource(it) != null).forEach(it -> {
final ResourceBundleModel model = new ResourceBundleModel();
model.setName(it.replace('/', '.').substring(0, it.length() - ".properties".length()));
context.register(model);
});
}
private void registerSPI(final Context context) {
spiClasses(context)
.distinct()
.map(Class::getName)
.forEach(it -> {
final ClassReflectionModel model = new ClassReflectionModel();
model.setName(it);
model.setAllPublicConstructors(true);
model.setAllDeclaredConstructors(true);
model.setAllPublicMethods(true);
context.register(model);
});
}
// todo: cut more of that by a review of the reflection.arthur.json
// one option is to precompute it for a pure openjpa deployment and just add all user impl only
private Stream<? extends Class<?>> spiClasses(final Context context) {
return Stream.of(
"org.apache.openjpa.kernel.BrokerFactory",
"org.apache.openjpa.lib.log.LogFactory",
"org.apache.openjpa.lib.conf.Configurable",
"org.apache.openjpa.util.CacheMap",
"org.apache.openjpa.event.SingleJVMRemoteCommitProvider",
"org.apache.openjpa.event.SingleJVMRemoteCommitProvider",
"org.apache.openjpa.persistence.jdbc.PersistenceMappingFactory",
"org.apache.openjpa.jdbc.kernel.TableJDBCSeq",
"org.apache.openjpa.jdbc.kernel.ValueTableJDBCSeq",
"org.apache.openjpa.jdbc.kernel.ClassTableJDBCSeq",
"org.apache.openjpa.jdbc.kernel.NativeJDBCSeq",
"org.apache.openjpa.kernel.TimeSeededSeq",
"org.apache.openjpa.jdbc.kernel.TableJDBCSeq",
"org.apache.openjpa.jdbc.kernel.ClassTableJDBCSeq",
"org.apache.openjpa.kernel.TimeSeededSeq",
"org.apache.openjpa.persistence.EntityManagerFactoryImpl",
"org.apache.openjpa.jdbc.meta.MappingRepository",
"org.apache.openjpa.meta.MetaDataRepository",
"org.apache.openjpa.util.ClassResolverImpl",
"org.apache.openjpa.datacache.DataCacheManagerImpl",
"org.apache.openjpa.datacache.DefaultCacheDistributionPolicy",
"org.apache.openjpa.datacache.ConcurrentDataCache",
"org.apache.openjpa.datacache.ConcurrentQueryCache",
"org.apache.openjpa.kernel.NoneLockManager",
"org.apache.openjpa.kernel.VersionLockManager",
"org.apache.openjpa.kernel.InverseManager",
"org.apache.openjpa.kernel.InMemorySavepointManager",
"org.apache.openjpa.event.LogOrphanedKeyAction",
"org.apache.openjpa.event.ExceptionOrphanedKeyAction",
"org.apache.openjpa.event.NoneOrphanedKeyAction",
"org.apache.openjpa.ee.AutomaticManagedRuntime",
"org.apache.openjpa.ee.JNDIManagedRuntime",
"org.apache.openjpa.ee.InvocationManagedRuntime",
"org.apache.openjpa.util.ProxyManagerImpl",
"org.apache.openjpa.conf.DetachOptions$Loaded",
"org.apache.openjpa.conf.DetachOptions$FetchGroups",
"org.apache.openjpa.conf.DetachOptions$All",
"org.apache.openjpa.conf.Compatibility",
"org.apache.openjpa.conf.CallbackOptions",
"org.apache.openjpa.event.LifecycleEventManager",
"org.apache.openjpa.validation.ValidatingLifecycleEventManager",
"org.apache.openjpa.instrumentation.InstrumentationManagerImpl",
"org.apache.openjpa.audit.AuditLogger",
"org.apache.openjpa.jdbc.sql.DBDictionary",
"org.apache.openjpa.jdbc.kernel.AbstractUpdateManager",
"org.apache.openjpa.jdbc.schema.DriverDataSource",
"org.apache.openjpa.jdbc.schema.DynamicSchemaFactory",
"org.apache.openjpa.jdbc.schema.LazySchemaFactory",
"org.apache.openjpa.jdbc.schema.FileSchemaFactory",
"org.apache.openjpa.jdbc.schema.TableSchemaFactory",
"org.apache.openjpa.jdbc.sql.SQLFactoryImpl",
"org.apache.openjpa.jdbc.meta.MappingDefaultsImpl",
"org.apache.openjpa.jdbc.kernel.PreparedQueryCacheImpl",
"org.apache.openjpa.jdbc.kernel.FinderCacheImpl",
"org.apache.openjpa.jdbc.identifier.DBIdentifierUtilImpl",
"org.apache.openjpa.lib.log.LogFactoryImpl",
"org.apache.openjpa.lib.log.SLF4JLogFactory",
"org.apache.openjpa.lib.log.NoneLogFactory",
"org.apache.openjpa.slice.DistributionPolicy$Default",
"org.apache.openjpa.slice.ReplicationPolicy$Default",
"javax.persistence.spi.PersistenceProvider")
.distinct()
.map(it -> {
try {
return context.loadClass(it);
} catch (final IllegalStateException | NoClassDefFoundError ise) {
return null;
}
})
.filter(Objects::nonNull)
.flatMap(context::findHierarchy)
.distinct()
.flatMap(it -> Stream.concat(Stream.of(it), context.findImplementations(it).stream()))
.distinct()
.filter(it -> needsReflection(it.getName()))
.flatMap(it -> {
if (it.getName().startsWith("org.apache.commons.logging.impl.")) {
try {
context.loadClass("org.apache.commons.logging.impl.LogFactoryImpl").getConstructor().newInstance();
return Stream.concat(Stream.of(it), addCommonsLogging(context));
} catch (final NoClassDefFoundError | Exception e) {
return Stream.empty();
}
}
return Stream.of(it);
})
.filter(it -> {
if ("org.apache.openjpa.jdbc.sql.PostgresDictionary".equals(it.getName())) {
try {
context.loadClass("org.postgresql.largeobject.LargeObjectManager");
return true;
} catch (final NoClassDefFoundError | Exception e) {
return false;
}
}
return true;
});
}
// todo: extract it in a commons-logging-knight and inherit from it automatically?
private Stream<? extends Class<?>> addCommonsLogging(final Context context) {
return Stream.of(
"org.apache.commons.logging.LogFactory",
"org.apache.commons.logging.impl.LogFactoryImpl",
"org.apache.commons.logging.impl.Jdk14Logger")
.map(n -> {
try {
return context.loadClass(n);
} catch (final NoClassDefFoundError | Exception ex) {
return null;
}
})
.filter(Objects::nonNull);
}
private boolean needsReflection(final String name) {
return !name.equals("org.apache.openjpa.ee.OSGiManagedRuntime") &&
!name.startsWith("org.apache.openjpa.ee.WAS") &&
!name.startsWith("org.slf4j.") &&
!name.startsWith("org.apache.commons.") &&
!name.startsWith("java.") &&
!name.startsWith("javax.") &&
!name.startsWith("jakarta.") &&
!name.contains("$util") &&
!name.contains("$1") &&
!name.startsWith("org.apache.openjpa.lib.jdbc.LoggingConnectionDecorator$") &&
!(name.endsWith("Comparator") && name.contains("$"));
}
private void registerJPAClasses(final Context context) {
Stream.of(Entity.class, MappedSuperclass.class, Embeddable.class)
.flatMap(it -> context.findAnnotatedClasses(it).stream())
.flatMap(context::findHierarchy)
.distinct()
.flatMap(it -> {
final ClassReflectionModel entity = new ClassReflectionModel();
entity.setName(it.getName());
entity.setAllPublicConstructors(true);
entity.setAllPublicMethods(true);
entity.setAllDeclaredConstructors(true);
entity.setAllDeclaredFields(true);
entity.setAllDeclaredMethods(true);
return Stream.concat(Stream.of(entity), extractFieldTypesForReflection(it));
})
.distinct()
.forEach(context::register);
}
private Stream<ClassReflectionModel> extractFieldTypesForReflection(final Class<?> entity) {
try {
final Field pcFieldTypes = entity.getDeclaredField("pcFieldTypes");
pcFieldTypes.setAccessible(true);
final Object types = pcFieldTypes.get(null);
return Stream.of(Class[].class.cast(types))
.distinct() // todo: filter(it -> !it.isPrimitive())?
.map(type -> {
final ClassReflectionModel fieldType = new ClassReflectionModel();
fieldType.setName(type.getName());
return fieldType;
});
} catch (final Exception e) {
return Stream.empty();
}
}
}