/*
* Copyright 2012 Michael Bischoff
*
* Licensed 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 de.jpaw.bonaparte.core;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import de.jpaw.bonaparte.pojos.meta.ClassDefinition;
public class BonaPortableFactory {
private static final Logger LOGGER = LoggerFactory.getLogger(BonaPortableFactory.class);
// Skip the next field and PathResolverTest may fail. Required to load the meta data classes in the correct order, required due to cyclic dependencies
// of static fields and their initialization.
public static Object UNUSED = ClassDefinition.class$MetaData();
// it will definitely fail if for example FieldDefinition is loaded before ClassDefinition
/** Stub to call to force initialization, in case no other initialization is required. */
public static void init() {
}
static private ConcurrentMap<String, BonaPortableClass<? extends BonaPortable>> mapByPQON = new ConcurrentHashMap<String, BonaPortableClass<? extends BonaPortable>>(2048);
static private ConcurrentMap<String, BonaPortableClass<? extends BonaPortable>> mapByFQON = new ConcurrentHashMap<String, BonaPortableClass<? extends BonaPortable>>(1024);
static private String bonaparteClassDefaultPackagePrefix = "de.jpaw.bonaparte.pojos";
static private Map<String, String> packagePrefixMap = new ConcurrentHashMap<String,String>(16);
static {
// mappings for bonaparte-core. Install a fresh map if you don't want these. Otherwise, add single mappings to them, or overwrite these
packagePrefixMap.put("bonaparte", "de.jpaw.bonaparte"); // bonaparte-core, sub-packages core, meta, ui
packagePrefixMap.put("meta", bonaparteClassDefaultPackagePrefix + ".meta"); // bonaparte-core
packagePrefixMap.put("ui", bonaparteClassDefaultPackagePrefix + ".ui"); // bonaparte-core
packagePrefixMap.put("api", bonaparteClassDefaultPackagePrefix + ".api"); // bonaparte-api
packagePrefixMap.put("apip", bonaparteClassDefaultPackagePrefix + ".apip"); // bonaparte-api (primitive long primary keys)
packagePrefixMap.put("apiw", bonaparteClassDefaultPackagePrefix + ".apiw"); // bonaparte-api (object wrapped long primary keys)
packagePrefixMap.put("adapters", bonaparteClassDefaultPackagePrefix + ".adapters"); // bonaparte-adapters-*
}
static private class HiddenClass {
}
static final private HiddenClass A_WAY_TO_GET_MY_CLASSLOADER = new HiddenClass();
static public final String BONAPARTE_DEFAULT_PACKAGE_PREFIX = "bonapartePrefix"; // "BONAPARTE_DEFAULT_PACKAGE_PREFIX"; // system property name which can be used as a source
static public boolean publishDefaultPrefix = true; // required in environments which instantiate multiple classloaders for isolation (vert.x)
static private boolean bonaparteClassDefaultPackagePrefixShouldBeRetrieved = true;
static private final AtomicInteger initializationCounter = new AtomicInteger();
// prevent instance creation
private BonaPortableFactory() {
}
/** Maps the partially qualified object name (PQON) into a fully qualified name / canonical name.
* Returns null if no mapping specific for this package is found.
* Examines the map packagePrefixMap, looking for specific packages first.
* This means it is possible to map test.zzz.* to another package than test.*.
* */
public static String mapPackage(String name) {
int lastDot = name.lastIndexOf('.'); // duplicate evaluation accepted for sake of API simplicity
while (lastDot > 0) {
String mappedPackagePart = packagePrefixMap.get(name.substring(0, lastDot));
if (mappedPackagePart != null) {
return mappedPackagePart + name.substring(lastDot);
}
// try another attempt, looking at a prior dot
lastDot = name.lastIndexOf('.', lastDot-1);
}
return null;
}
/** For a partially qualified name, return the fully qualified name of the class.
* Uses the default prefix no no specific mapping has been found. */
public static String mapPqonToFqon(String name) throws MessageParserException {
String FQON = null;
int lastDot = name.lastIndexOf('.');
if ((lastDot == 0) || (lastDot >= (name.length() - 1))) {
throw new MessageParserException(MessageParserException.BAD_OBJECT_NAME, null, -1, name);
}
if (packagePrefixMap != null && lastDot > 0) {
FQON = mapPackage(name); // attempt, perhaps returns null
}
if (FQON != null)
return FQON;
else
return getBonaparteClassDefaultPackagePrefix() + "." + name;
}
// generalized factory: create an instance of the requested type, which is specified by partially qualified name.
// We receive PQCN (partially qualified class name) as parameter.
// Anything before the last '.' is the Bonaparte package name (which is null is there is no '.').
// The package determines the possible bundle specification, not-null bundles are loaded in their
// own classloaders, so they can be unloaded again.
// Package to bundle mapping is contained in the static class data, however we cannot known that
// before actually loading the class. Therefore, bundle information must be fed in separately
// and can only be consistency-checked afterwards.
// new method, caches the BClass, to avoid reflection to create a new instance
// parameter name is the PQON of the desired class
public static BonaPortable createObject(String pqon) throws MessageParserException {
BonaPortableClass<? extends BonaPortable> bclass = mapByPQON.get(pqon);
if (bclass != null)
return bclass.newInstance(); // new instance without reflection
// determine fully qualified pqon of class and use reflection to retrieve an instance the first time
BonaPortable instance = createObjectSub(mapPqonToFqon(pqon));
// store it in the cache for next time
mapByPQON.putIfAbsent(pqon, instance.ret$BonaPortableClass());
return instance;
}
public static BonaPortable createObjectByFqon(String fqon) throws MessageParserException {
BonaPortableClass<? extends BonaPortable> bclass = mapByFQON.get(fqon);
if (bclass != null)
return bclass.newInstance(); // new instance without reflection
// determine fully qualified pqon of class and use reflection to retrieve an instance the first time
BonaPortable instance = createObjectSub(fqon);
// store it in the cache for next time
mapByFQON.putIfAbsent(fqon, instance.ret$BonaPortableClass());
return instance;
}
private static BonaPortable createObjectSub(String FQON) throws MessageParserException {
try {
LOGGER.debug("Factory: loading class {}", FQON);
Class<? extends BonaPortable> f = Class.forName(FQON, true, Thread.currentThread().getContextClassLoader()).asSubclass(BonaPortable.class);
return f.newInstance();
} catch (ClassNotFoundException e) {
LOGGER.error("ClassNotFound exception for {}", FQON);
logClassloaders();
} catch (InstantiationException e) {
LOGGER.error("Instantiation exception for {}", FQON);
logClassloaders();
} catch (IllegalAccessException e) {
LOGGER.error("IllegalAccess exception for {}", FQON);
logClassloaders();
}
throw new MessageParserException(MessageParserException.CLASS_NOT_FOUND, "class", 0, FQON);
}
public static BonaPortableClass<? extends BonaPortable> getBClassForPqon(String pqon) throws MessageParserException {
BonaPortableClass<? extends BonaPortable> bclass = mapByPQON.get(pqon);
if (bclass == null) {
bclass = createObjectSub(mapPqonToFqon(pqon)).ret$BonaPortableClass();
// also cache it now
mapByPQON.putIfAbsent(pqon, bclass);
}
return bclass;
}
public static BonaPortableClass<? extends BonaPortable> getBClassForFqon(String fqon) throws MessageParserException {
BonaPortableClass<? extends BonaPortable> bclass = mapByFQON.get(fqon);
if (bclass == null) {
bclass = createObjectSub(fqon).ret$BonaPortableClass();
// also cache it now
mapByFQON.putIfAbsent(fqon, bclass);
}
return bclass;
}
// auto getters and setters only following
public static Map<String, String> getPackagePrefixMap() {
return packagePrefixMap;
}
public static void setPackagePrefixMap(Map<String, String> packagePrefixMap) {
BonaPortableFactory.packagePrefixMap = packagePrefixMap;
}
public static void logClassloaders() {
LOGGER.info("My classloader is {}, thread context classloader is {}",
A_WAY_TO_GET_MY_CLASSLOADER.getClass().getClassLoader().toString(),
Thread.currentThread().getContextClassLoader().toString());
}
public static String getBonaparteClassDefaultPackagePrefix() {
if (bonaparteClassDefaultPackagePrefixShouldBeRetrieved) {
String possibleOverride = System.getProperty(BONAPARTE_DEFAULT_PACKAGE_PREFIX);
// bonaparteClassDefaultPackagePrefix = System.getProperty(BONAPARTE_DEFAULT_PACKAGE_PREFIX, bonaparteClassDefaultPackagePrefix);
if (possibleOverride != null) {
bonaparteClassDefaultPackagePrefix = possibleOverride;
LOGGER.info("Setting default package prefix to {} from system property", bonaparteClassDefaultPackagePrefix);
if (LOGGER.isDebugEnabled())
logClassloaders();
}
bonaparteClassDefaultPackagePrefixShouldBeRetrieved = false;
}
return bonaparteClassDefaultPackagePrefix;
}
public static void setBonaparteClassDefaultPackagePrefix(
String bonaparteClassDefaultPackagePrefix) {
BonaPortableFactory.bonaparteClassDefaultPackagePrefix = bonaparteClassDefaultPackagePrefix;
bonaparteClassDefaultPackagePrefixShouldBeRetrieved = false; // I got it from the application now
if (publishDefaultPrefix) {
LOGGER.info("Publishing new default package prefix {}, count = {}", bonaparteClassDefaultPackagePrefix, initializationCounter.incrementAndGet());
if (LOGGER.isDebugEnabled())
logClassloaders();
System.setProperty(BONAPARTE_DEFAULT_PACKAGE_PREFIX, bonaparteClassDefaultPackagePrefix);
}
}
public static String addToPackagePrefixMap(String packagePrefix, String newFullPackage) {
if (newFullPackage == null) {
// remove a mapping.
return packagePrefixMap.remove(packagePrefix);
} else {
return packagePrefixMap.put(packagePrefix, newFullPackage);
}
}
}