org.structr.module.JarConfigurationProvider Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of structr-core Show documentation
Show all versions of structr-core Show documentation
Structr is an open source framework based on the popular Neo4j graph database.
The newest version!
/**
* Copyright (C) 2010-2016 Structr GmbH
*
* This file is part of Structr .
*
* Structr is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* Structr is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Structr. If not, see .
*/
package org.structr.module;
import org.structr.api.service.Service;
import org.structr.agent.Agent;
import org.structr.core.entity.AbstractNode;
import org.structr.core.entity.AbstractRelationship;
import org.structr.core.entity.GenericNode;
//~--- JDK imports ------------------------------------------------------------
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.apache.commons.lang3.StringUtils;
import org.structr.common.DefaultFactoryDefinition;
import org.structr.common.FactoryDefinition;
import org.structr.common.PropertyView;
import org.structr.common.SecurityContext;
import org.structr.common.View;
import org.structr.core.*;
import org.structr.core.entity.Relation;
import org.structr.core.graph.NodeInterface;
import org.structr.core.graph.RelationshipInterface;
import org.structr.core.property.GenericProperty;
import org.structr.core.property.PropertyKey;
import org.structr.schema.ConfigurationProvider;
import org.structr.schema.SchemaService;
import org.structr.util.LogMessageSupplier;
//~--- classes ----------------------------------------------------------------
/**
* The module service main class.
*
*
*/
public class JarConfigurationProvider implements ConfigurationProvider {
private static final Logger logger = Logger.getLogger(JarConfigurationProvider.class.getName());
public static final String DYNAMIC_TYPES_PACKAGE = "org.structr.dynamic";
private final Map> relationshipEntityClassCache = new ConcurrentHashMap<>(1000);
private final Map> nodeEntityClassCache = new ConcurrentHashMap(1000);
private final Map> agentClassCache = new ConcurrentHashMap<>(100);
private final Set agentPackages = new LinkedHashSet<>();
private final Set nodeEntityPackages = new LinkedHashSet<>();
private final Set relationshipPackages = new LinkedHashSet<>();
private final Map combinedTypeRelationClassCache = new ConcurrentHashMap<>(100);
private final Map> interfaceCache = new ConcurrentHashMap<>(2000);
private final String fileSep = System.getProperty("file.separator");
private final String pathSep = System.getProperty("path.separator");
private final String fileSepEscaped = fileSep.replaceAll("\\\\", "\\\\\\\\");
private final String testClassesDir = fileSep.concat("test-classes");
private final String classesDir = fileSep.concat("classes");
private final Map>> globalPropertyViewMap = new ConcurrentHashMap<>(2000);
private final Map>> globalValidatorMap = new ConcurrentHashMap<>(100);
private final Map> globalClassDBNamePropertyMap = new ConcurrentHashMap<>(2000);
private final Map> globalClassJSNamePropertyMap = new ConcurrentHashMap<>(2000);
private final Map> globalAggregatedPropertyGroupMap = new ConcurrentHashMap<>(100);
private final Map> globalPropertyGroupMap = new ConcurrentHashMap<>(100);
private final Map> viewTransformations = new ConcurrentHashMap<>(100);
private final Map>> globalTransformationMap = new ConcurrentHashMap<>(100);
private final Map> exportedMethodMap = new ConcurrentHashMap<>(100);
private final Map> interfaceMap = new ConcurrentHashMap<>(2000);
private final Map reverseInterfaceMap = new ConcurrentHashMap<>(5000);
private final Set globalKnownPropertyKeys = new LinkedHashSet<>();
private final Set dynamicViews = new LinkedHashSet<>();
private FactoryDefinition factoryDefinition = new DefaultFactoryDefinition();
// ----- interface Configuration -----
@Override
public void initialize() {
scanResources();
}
@Override
public void shutdown() {
/* do not clear caches
nodeEntityClassCache.clear();
combinedTypeRelationClassCache.clear();
relationshipEntityClassCache.clear();
agentClassCache.clear();
*/
}
@Override
public Map> getAgents() {
return agentClassCache;
}
@Override
public Map> getNodeEntities() {
synchronized (SchemaService.class) {
return nodeEntityClassCache;
}
}
@Override
public Map> getRelationshipEntities() {
synchronized (SchemaService.class) {
return relationshipEntityClassCache;
}
}
@Override
public Set getClassesForInterface(final String simpleName) {
synchronized (SchemaService.class) {
return interfaceCache.get(simpleName);
}
}
@Override
public Class getNodeEntityClass(final String simpleName) {
Class nodeEntityClass = GenericNode.class;
if ((simpleName != null) && (!simpleName.isEmpty())) {
synchronized (SchemaService.class) {
nodeEntityClass = nodeEntityClassCache.get(simpleName);
if (nodeEntityClass == null) {
for (String possiblePath : nodeEntityPackages) {
if (possiblePath != null) {
try {
Class nodeClass = Class.forName(possiblePath + "." + simpleName);
if (!Modifier.isAbstract(nodeClass.getModifiers())) {
nodeEntityClassCache.put(simpleName, nodeClass);
nodeEntityClass = nodeClass;
// first match wins
break;
}
} catch (ClassNotFoundException ex) {
// ignore
}
}
}
}
}
}
return nodeEntityClass;
}
@Override
public Class getRelationshipEntityClass(final String name) {
Class relationClass = AbstractRelationship.class;
if ((name != null) && (name.length() > 0)) {
synchronized (SchemaService.class) {
relationClass = relationshipEntityClassCache.get(name);
if (relationClass == null) {
for (String possiblePath : relationshipPackages) {
if (possiblePath != null) {
try {
Class nodeClass = Class.forName(possiblePath + "." + name);
if (!Modifier.isAbstract(nodeClass.getModifiers())) {
relationshipEntityClassCache.put(name, nodeClass);
// first match wins
return nodeClass;
}
} catch (ClassNotFoundException ex) {
// ignore
}
}
}
}
}
}
return relationClass;
}
public Class extends Agent> getAgentClass(final String name) {
Class agentClass = null;
if ((name != null) && (name.length() > 0)) {
agentClass = agentClassCache.get(name);
if (agentClass == null) {
for (String possiblePath : agentPackages) {
if (possiblePath != null) {
try {
Class nodeClass = Class.forName(possiblePath + "." + name);
agentClassCache.put(name, nodeClass);
// first match wins
return nodeClass;
} catch (ClassNotFoundException ex) {
// ignore
}
}
}
}
}
return agentClass;
}
@Override
public Map getInterfaces() {
return reverseInterfaceMap;
}
@Override
public void setRelationClassForCombinedType(final String combinedType, final Class clazz) {
combinedTypeRelationClassCache.put(combinedType, clazz);
}
@Override
public void setRelationClassForCombinedType(final String sourceType, final String relType, final String targetType, final Class clazz) {
combinedTypeRelationClassCache.put(getCombinedType(sourceType, relType, targetType), clazz);
}
private Class getRelationClassForCombinedType(final String combinedType) {
Class cachedRelationClass = combinedTypeRelationClassCache.get(combinedType);
if (cachedRelationClass != null) {
return cachedRelationClass;
}
return null;
}
@Override
public Class getRelationClassForCombinedType(final String sourceTypeName, final String relType, final String targetTypeName) {
if (sourceTypeName == null || relType == null || targetTypeName == null) {
return null;
}
String combinedType
= sourceTypeName
.concat(DefaultFactoryDefinition.COMBINED_RELATIONSHIP_KEY_SEP)
.concat(relType)
.concat(DefaultFactoryDefinition.COMBINED_RELATIONSHIP_KEY_SEP)
.concat(targetTypeName);
Class cachedRelationClass = getRelationClassForCombinedType(combinedType);
if (cachedRelationClass != null) {
return cachedRelationClass;
}
return findNearestMatchingRelationClass(sourceTypeName, relType, targetTypeName);
}
/**
* Return a list of all relation entity classes filtered by relationship
* type.
*
* @param relType
* @return classes
*/
private List> getRelationClassCandidatesForRelType(final String relType) {
List> candidates = new ArrayList();
for (final Class extends RelationshipInterface> candidate : getRelationshipEntities().values()) {
Relation rel = instantiate(candidate);
if (rel == null) {
continue;
}
if (rel.name().equals(relType)) {
candidates.add(candidate);
}
}
return candidates;
}
/**
* Find the most specialized relation class matching the given
* parameters.
*
* If no direct match is found (source and target type are equal), we
* count the levels of inheritance, including interfaces.
*
* @param sourceTypeName
* @param relType
* @param targetTypeName
* @param rel
* @param candidate
* @return class
*/
private Class findNearestMatchingRelationClass(final String sourceTypeName, final String relType, final String targetTypeName) {
//System.out.println("###### Find nearest matching relation class for " + sourceTypeName + " " + relType + " " + targetTypeName);
Map candidates = new TreeMap<>();
Class sourceType = getNodeEntityClass(sourceTypeName);
Class targetType = getNodeEntityClass(targetTypeName);
for (final Class candidate : getRelationClassCandidatesForRelType(relType)) {
Relation rel = instantiate(candidate);
//System.out.println("? " + candidate.getSimpleName() + " for [" + sourceTypeName + " " + relType + " " + targetTypeName + "]");
int distance = getDistance(rel.getSourceType(), sourceType, -1) + getDistance(rel.getTargetType(), targetType, -1);
if (distance >= 2000) {
candidates.put(distance - 2000, candidate);
//System.out.println("\n=========================== Found " + candidate.getSimpleName() + " for " + sourceTypeName + " " + relType + " " + targetTypeName + " at distance " + (distance-2000));
} else {
//System.out.println(" no match.");
}
}
if (candidates.isEmpty()) {
//System.out.println("!!!!!!! No matching relation class found for " + sourceTypeName + " " + relType + " " + targetTypeName);
return null;
} else {
Entry candidateEntry = candidates.entrySet().iterator().next();
Class c = candidateEntry.getValue();
//System.out.println("########### Final nearest relation class : " + c.getSimpleName() + " <" + candidateEntry.getKey() + ">############################################\n\n");
combinedTypeRelationClassCache.put(getCombinedType(sourceTypeName, relType, targetTypeName), c);
return c;
}
}
private int getDistance(final Class candidateType, final Class type, int distance) {
if (distance >= 1000) {
return distance;
}
distance++;
// Just in case...
if (type == null) {
return Integer.MIN_VALUE;
}
//System.out.print(".");
// Abort if type is Object.class here
if (type.equals(Object.class)) {
return Integer.MIN_VALUE;
}
//System.out.print(".");
//System.out.print(candidateType.getSimpleName() + "<" + distance + ">");
//System.out.print(".");
// Check direct equality
if (type.equals(candidateType)) {
//System.out.print("MATCH<" + distance + ">!");
return distance + 1000;
}
// Abort here if type is NodeInterface.
if (type.equals(NodeInterface.class)) {
return Integer.MIN_VALUE;
}
//System.out.print(".");
// Relation candidate's source and target types must be superclasses or interfaces of the given relationship
if (!(candidateType.isAssignableFrom(type))) {
return Integer.MIN_VALUE;
}
//System.out.print(".");
distance++;
// Test source's interfaces against target class
Class[] interfaces = type.getInterfaces();
for (Class iface : interfaces) {
//System.out.print("." + iface.getSimpleName() + "<" + distance + ">" + "(SI).");
if (iface.equals(candidateType)) {
//System.out.print("MATCH<" + distance + ">!");
return distance + 1000;
}
}
distance++;
Class superClass = type.getSuperclass();
if (superClass != null) {
//System.out.println("." + superClass.getSimpleName() + "<" + distance + ">");
int d = getDistance(candidateType, superClass, distance);
if (d >= 1000) {
return d;
}
}
return distance;
}
@Override
public Map getAnnotatedMethods(Class entityType, Class annotationType) {
Map methods = new HashMap<>();
Set> allTypes = getAllTypes(entityType);
for (Class> type : allTypes) {
for (Method method : type.getDeclaredMethods()) {
if (method.getAnnotation(annotationType) != null) {
methods.put(method.getName(), method);
}
}
}
return methods;
}
@Override
public void unregisterEntityType(final Class oldType) {
synchronized (SchemaService.class) {
final String simpleName = oldType.getSimpleName();
final String fqcn = oldType.getName();
nodeEntityClassCache.remove(simpleName);
relationshipEntityClassCache.remove(simpleName);
nodeEntityPackages.remove(fqcn);
relationshipPackages.remove(fqcn);
globalPropertyViewMap.remove(fqcn);
globalClassDBNamePropertyMap.remove(fqcn);
globalClassJSNamePropertyMap.remove(fqcn);
interfaceMap.remove(oldType);
// clear all
combinedTypeRelationClassCache.clear();
// clear interfaceCache manually..
for (final Set classes : interfaceCache.values()) {
if (classes.contains(oldType)) {
classes.remove(oldType);
}
}
}
}
@Override
public void registerEntityType(final Class type) {
// moved here from scanEntity, no reason to have this in a separate
// method requiring two different calls instead of one
String simpleName = type.getSimpleName();
String fqcn = type.getName();
if (AbstractNode.class.isAssignableFrom(type)) {
nodeEntityClassCache.put(simpleName, type);
nodeEntityPackages.add(fqcn.substring(0, fqcn.lastIndexOf(".")));
globalPropertyViewMap.remove(fqcn);
}
if (AbstractRelationship.class.isAssignableFrom(type)) {
relationshipEntityClassCache.put(simpleName, type);
relationshipPackages.add(fqcn.substring(0, fqcn.lastIndexOf(".")));
globalPropertyViewMap.remove(fqcn);
}
for (Class interfaceClass : type.getInterfaces()) {
String interfaceName = interfaceClass.getSimpleName();
Set classesForInterface = interfaceCache.get(interfaceName);
if (classesForInterface == null) {
classesForInterface = new LinkedHashSet<>();
interfaceCache.put(interfaceName, classesForInterface);
}
classesForInterface.add(type);
}
try {
final Map allProperties = getFieldValuesOfType(PropertyKey.class, type);
final Map views = getFieldValuesOfType(View.class, type);
for (final Map.Entry entry : allProperties.entrySet()) {
final PropertyKey propertyKey = entry.getValue();
final Field field = entry.getKey();
final Class declaringClass = field.getDeclaringClass();
if (declaringClass != null) {
propertyKey.setDeclaringClass(declaringClass);
registerProperty(declaringClass, propertyKey);
}
registerProperty(type, propertyKey);
}
for (Map.Entry entry : views.entrySet()) {
final Field field = entry.getKey();
final View view = entry.getValue();
for (PropertyKey propertyKey : view.properties()) {
// register field in view for entity class and declaring superclass
registerPropertySet(field.getDeclaringClass(), view.name(), propertyKey);
registerPropertySet(type, view.name(), propertyKey);
}
}
} catch (Throwable t) {
logger.log(Level.SEVERE, t, LogMessageSupplier.create("Unable to register type {0}: {1}", new Object[]{type, t.getMessage()}));
}
Map typeMethods = exportedMethodMap.get(fqcn);
if (typeMethods == null) {
typeMethods = new HashMap<>();
exportedMethodMap.put(fqcn, typeMethods);
}
typeMethods.putAll(getAnnotatedMethods(type, Export.class));
// extract interfaces for later use
getInterfacesForType(type);
}
/**
* Register a transformation that will be applied to every newly created
* entity of a given type.
*
* @param type the type of the entities for which the transformation
* should be applied
* @param transformation the transformation to apply on every entity
*/
@Override
public void registerEntityCreationTransformation(Class type, Transformation transformation) {
final Set> transformations = getEntityCreationTransformationsForType(type);
if (!transformations.contains(transformation)) {
transformations.add(transformation);
}
}
@Override
public Set getInterfacesForType(Class type) {
Set interfaces = interfaceMap.get(type);
if (interfaces == null) {
interfaces = new LinkedHashSet<>();
interfaceMap.put(type, interfaces);
for (Class iface : type.getInterfaces()) {
reverseInterfaceMap.put(iface.getSimpleName(), iface);
interfaces.add(iface);
}
}
return interfaces;
}
@Override
public Map getExportedMethodsForType(Class type) {
return exportedMethodMap.get(type.getName());
}
@Override
public boolean isKnownProperty(final PropertyKey key) {
return globalKnownPropertyKeys.contains(key);
}
@Override
public FactoryDefinition getFactoryDefinition() {
return factoryDefinition;
}
@Override
public void registerFactoryDefinition(FactoryDefinition factory) {
factoryDefinition = factory;
}
/**
* Registers a property group for the given key of the given entity
* type. A property group can be used to combine a set of properties
* into an object.
*
* @param type the type of the entities for which the property group
* should be registered
* @param key the property key under which the property group should be
* visible
* @param propertyGroup the property group
*/
@Override
public void registerPropertyGroup(Class type, PropertyKey key, PropertyGroup propertyGroup) {
getPropertyGroupMapForType(type).put(key.dbName(), propertyGroup);
}
@Override
public void registerConvertedProperty(PropertyKey propertyKey) {
globalKnownPropertyKeys.add(propertyKey);
}
@Override
public synchronized Set> getEntityCreationTransformations(Class type) {
Set> transformations = new TreeSet<>();
Class localType = type;
// collect for all superclasses
while (localType != null && !localType.equals(Object.class)) {
transformations.addAll(getEntityCreationTransformationsForType(localType));
localType = localType.getSuperclass();
}
return transformations;
}
@Override
public PropertyGroup getPropertyGroup(Class type, PropertyKey key) {
return getPropertyGroup(type, key.dbName());
}
@Override
public PropertyGroup getPropertyGroup(Class type, String key) {
PropertyGroup group = getAggregatedPropertyGroupMapForType(type).get(key);
if (group == null) {
Class localType = type;
while (group == null && localType != null && !localType.equals(Object.class)) {
group = getPropertyGroupMapForType(localType).get(key);
if (group == null) {
// try interfaces as well
for (Class interfaceClass : getInterfacesForType(localType)) {
group = getPropertyGroupMapForType(interfaceClass).get(key);
if (group != null) {
break;
}
}
}
localType = localType.getSuperclass();
}
getAggregatedPropertyGroupMapForType(type).put(key, group);
}
return group;
}
@Override
public void registerViewTransformation(Class type, String view, ViewTransformation transformation) {
getViewTransformationMapForType(type).put(view, transformation);
}
@Override
public ViewTransformation getViewTransformation(Class type, String view) {
return getViewTransformationMapForType(type).get(view);
}
@Override
public Set getPropertyViews() {
Set views = new LinkedHashSet<>();
// add all existing views
for (Map> view : globalPropertyViewMap.values()) {
views.addAll(view.keySet());
}
// merge dynamic views in as well
views.addAll(dynamicViews);
return Collections.unmodifiableSet(views);
}
@Override
public Set getPropertyViewsForType(final Class type) {
final Map> map = getPropertyViewMapForType(type);
if (map != null) {
return map.keySet();
}
return Collections.emptySet();
}
@Override
public void registerDynamicViews(final Set dynamicViews) {
this.dynamicViews.clear();
this.dynamicViews.addAll(dynamicViews);
}
@Override
public Set getPropertySet(Class type, String propertyView) {
Map> propertyViewMap = getPropertyViewMapForType(type);
Set properties = propertyViewMap.get(propertyView);
if (properties == null) {
properties = new LinkedHashSet<>();
}
// read-only
return Collections.unmodifiableSet(properties);
}
/**
* Registers the given set of property keys for the view with name
* propertyView
and the given prefix of entities with the
* given type.
*
* @param type the type of the entities for which the view will be
* registered
* @param propertyView the name of the property view for which the
* property set will be registered
* @param propertySet the set of property keys to register for the given
* view
*/
@Override
public void registerPropertySet(Class type, String propertyView, PropertyKey... propertySet) {
Map> propertyViewMap = getPropertyViewMapForType(type);
Set properties = propertyViewMap.get(propertyView);
if (properties == null) {
properties = new LinkedHashSet<>();
propertyViewMap.put(propertyView, properties);
}
// allow properties to override existing ones as they
// are most likely from a more concrete class.
for (final PropertyKey key : propertySet) {
// property keys are referenced by their names,
// that's why we seemingly remove the existing
// key, but the set does not differentiate
// between different keys
if (properties.contains(key)) {
properties.remove(key);
}
properties.add(key);
}
}
@Override
public void registerPropertySet(final Class type, final String propertyView, final String propertyName) {
this.registerPropertySet(type, propertyView, this.getPropertyKeyForJSONName(type, propertyName));
}
@Override
public PropertyKey getPropertyKeyForDatabaseName(Class type, String dbName) {
return getPropertyKeyForDatabaseName(type, dbName, true);
}
@Override
public PropertyKey getPropertyKeyForDatabaseName(Class type, String dbName, boolean createGeneric) {
Map classDBNamePropertyMap = getClassDBNamePropertyMapForType(type);
PropertyKey key = classDBNamePropertyMap.get(dbName);
if (key == null) {
// first try: uuid
if (GraphObject.id.dbName().equals(dbName)) {
return GraphObject.id;
}
if (createGeneric) {
key = new GenericProperty(dbName);
}
}
return key;
}
@Override
public PropertyKey getPropertyKeyForJSONName(Class type, String jsonName) {
return getPropertyKeyForJSONName(type, jsonName, true);
}
@Override
public PropertyKey getPropertyKeyForJSONName(Class type, String jsonName, boolean createIfNotFound) {
if (jsonName == null) {
return null;
}
Map classJSNamePropertyMap = getClassJSNamePropertyMapForType(type);
PropertyKey key = classJSNamePropertyMap.get(jsonName);
if (key == null) {
// first try: uuid
if (GraphObject.id.dbName().equals(jsonName)) {
return GraphObject.id;
}
if (createIfNotFound) {
key = new GenericProperty(jsonName);
}
}
return key;
}
@Override
public Set getPropertyValidators(final SecurityContext securityContext, Class type, PropertyKey propertyKey) {
Set validators = new LinkedHashSet<>();
Map> validatorMap = null;
Class localType = type;
// try all superclasses
while (localType != null && !localType.equals(Object.class)) {
validatorMap = getPropertyValidatorMapForType(localType);
Set classValidators = validatorMap.get(propertyKey);
if (classValidators != null) {
validators.addAll(validatorMap.get(propertyKey));
}
// try converters from interfaces as well
for (Class interfaceClass : getInterfacesForType(localType)) {
Set interfaceValidators = getPropertyValidatorMapForType(interfaceClass).get(propertyKey);
if (interfaceValidators != null) {
validators.addAll(interfaceValidators);
}
}
// logger.log(Level.INFO, "Validator class {0} found for type {1}", new Object[] { clazz != null ? clazz.getSimpleName() : "null", localType } );
// one level up :)
localType = localType.getSuperclass();
}
return validators;
}
@Override
public void registerProperty(Class type, PropertyKey propertyKey) {
getClassDBNamePropertyMapForType(type).put(propertyKey.dbName(), propertyKey);
getClassJSNamePropertyMapForType(type).put(propertyKey.jsonName(), propertyKey);
registerPropertySet(type, PropertyView.All, propertyKey);
// inform property key of its registration
propertyKey.registrationCallback(type);
}
@Override
public void registerDynamicProperty(Class type, PropertyKey propertyKey) {
synchronized (SchemaService.class) {
final String typeName = type.getName();
registerProperty(type, propertyKey);
// scan all existing classes and find all classes that have the given
// type as a supertype
for (final Class possibleSubclass : nodeEntityClassCache.values()) {
// need to compare strings not classes here..
for (final Class supertype : getAllTypes(possibleSubclass)) {
if (supertype.getName().equals(typeName)) {
registerProperty(possibleSubclass, propertyKey);
registerPropertySet(possibleSubclass, PropertyView.Ui, propertyKey);
}
}
}
}
}
// ----- private methods -----
private void scanResources() {
Set resourcePaths = getResourcesToScan();
for (String resourcePath : resourcePaths) {
scanResource(resourcePath);
}
logger.log(Level.INFO, "{0} JARs scanned", resourcePaths.size());
}
private void scanResource(String resourceName) {
try {
Module module = loadResource(resourceName);
if (module != null) {
importResource(module);
} else {
logger.log(Level.WARNING, "Module was null!");
}
} catch (IOException ioex) {
logger.log(Level.WARNING, ioex, LogMessageSupplier.create("Error loading module {0}: {1}", new Object[]{resourceName, ioex.getMessage()}));
}
}
private void importResource(Module module) throws IOException {
final Set classes = module.getClasses();
for (final String name : classes) {
String className = StringUtils.removeStart(name, ".");
logger.log(Level.FINE, "Instantiating class {0} ", className);
try {
// instantiate class..
Class clazz = Class.forName(className);
int modifiers = clazz.getModifiers();
logger.log(Level.FINE, "Class {0} instantiated: {1}", new Object[]{className, clazz});
// register node entity classes
if (NodeInterface.class.isAssignableFrom(clazz)) {
registerEntityType(clazz);
}
// register entity classes
if (AbstractRelationship.class.isAssignableFrom(clazz) && !(Modifier.isAbstract(modifiers))) {
registerEntityType(clazz);
}
// register services
if (Service.class.isAssignableFrom(clazz) && !(Modifier.isAbstract(modifiers))) {
Services.getInstance().registerServiceClass(clazz);
}
// register agents
if (Agent.class.isAssignableFrom(clazz) && !(Modifier.isAbstract(modifiers))) {
String simpleName = clazz.getSimpleName();
String fullName = clazz.getName();
agentClassCache.put(simpleName, clazz);
agentPackages.add(fullName.substring(0, fullName.lastIndexOf(".")));
}
} catch (Throwable t) {
}
}
}
private Module loadResource(String resource) throws IOException {
// create module
DefaultModule ret = new DefaultModule(resource);
Set classes = ret.getClasses();
if (resource.endsWith(".jar") || resource.endsWith(".war")) {
ZipFile zipFile = new ZipFile(new File(resource), ZipFile.OPEN_READ);
// conventions that might be useful here:
// ignore entries beginning with meta-inf/
// handle entries beginning with images/ as IMAGE
// handle entries beginning with pages/ as PAGES
// handle entries ending with .jar as libraries, to be deployed to WEB-INF/lib
// handle other entries as potential page and/or entity classes
// .. to be extended
// (entries that end with "/" are directories)
for (Enumeration extends ZipEntry> entries = zipFile.entries(); entries.hasMoreElements();) {
ZipEntry entry = entries.nextElement();
String entryName = entry.getName();
if (entryName.endsWith(".class")) {
String fileEntry = entry.getName().replaceAll("[/]+", ".");
// add class entry to Module
classes.add(fileEntry.substring(0, fileEntry.length() - 6));
}
}
zipFile.close();
} else if (resource.endsWith(classesDir)) {
addClassesRecursively(new File(resource), classesDir, classes);
} else if (resource.endsWith(testClassesDir)) {
addClassesRecursively(new File(resource), testClassesDir, classes);
}
return ret;
}
private void addClassesRecursively(File dir, String prefix, Set classes) {
if (dir == null) {
return;
}
int prefixLen = prefix.length();
File[] files = dir.listFiles();
if (files == null) {
return;
}
for (File file : files) {
if (file.isDirectory()) {
addClassesRecursively(file, prefix, classes);
} else {
try {
String fileEntry = file.getAbsolutePath();
fileEntry = fileEntry.substring(0, fileEntry.length() - 6);
fileEntry = fileEntry.substring(fileEntry.indexOf(prefix) + prefixLen);
fileEntry = fileEntry.replaceAll("[".concat(fileSepEscaped).concat("]+"), ".");
if (fileEntry.startsWith(".")) {
fileEntry = fileEntry.substring(1);
}
classes.add(fileEntry);
} catch (Throwable t) {
// ignore
logger.log(Level.WARNING, "", t);
}
}
}
}
private Relation instantiate(final Class clazz) {
try {
return (Relation) clazz.newInstance();
} catch (Throwable t) {
// ignore
//logger.log(Level.WARNING, "", t);
}
return null;
}
private String getCombinedType(final String sourceType, final String relType, final String targetType) {
return sourceType
.concat(DefaultFactoryDefinition.COMBINED_RELATIONSHIP_KEY_SEP)
.concat(relType)
.concat(DefaultFactoryDefinition.COMBINED_RELATIONSHIP_KEY_SEP)
.concat(targetType);
}
/**
* Scans the class path and returns a Set containing all structr
* modules.
*
* @return a Set of active module names
*/
private Set getResourcesToScan() {
String classPath = System.getProperty("java.class.path");
Set modules = new LinkedHashSet<>();
Pattern pattern = Pattern.compile(".*(structr).*(war|jar)");
Matcher matcher = pattern.matcher("");
for (String jarPath : classPath.split("[".concat(pathSep).concat("]+"))) {
String lowerPath = jarPath.toLowerCase();
if (lowerPath.endsWith(classesDir) || lowerPath.endsWith(testClassesDir)) {
modules.add(jarPath);
} else {
String moduleName = lowerPath.substring(lowerPath.lastIndexOf(pathSep) + 1);
matcher.reset(moduleName);
if (matcher.matches()) {
modules.add(jarPath);
}
}
}
for (String resource : Services.getInstance().getResources()) {
String lowerResource = resource.toLowerCase();
if (lowerResource.endsWith(".jar") || lowerResource.endsWith(".war")) {
modules.add(resource);
}
}
return modules;
}
private Map getFieldValuesOfType(Class fieldType, Class entityType) {
Map fields = new LinkedHashMap<>();
Set> allTypes = getAllTypes(entityType);
for (Class> type : allTypes) {
for (Field field : type.getDeclaredFields()) {
// only use static fields, because field.get(null) will throw a NPE on non-static fields
if (fieldType.isAssignableFrom(field.getType()) && Modifier.isStatic(field.getModifiers())) {
try {
// ensure access
field.setAccessible(true);
// fetch value
final T value = (T) field.get(null);
if (value != null) {
fields.put(field, value);
}
} catch (Throwable t) {
// ignore
}
}
}
}
return fields;
}
private Set> getAllTypes(Class> type) {
List> types = new LinkedList<>();
Class localType = type;
do {
collectAllInterfaces(localType, types);
types.add(localType);
localType = localType.getSuperclass();
} while (localType != null && !localType.equals(Object.class));
Collections.reverse(types);
return new LinkedHashSet<>(types);
}
private void collectAllInterfaces(Class> type, List> interfaces) {
if (interfaces.contains(type)) {
return;
}
for (Class iface : type.getInterfaces()) {
collectAllInterfaces(iface, interfaces);
interfaces.add(iface);
}
}
private Map> getPropertyViewMapForType(Class type) {
Map> propertyViewMap = globalPropertyViewMap.get(type.getName());
if (propertyViewMap == null) {
propertyViewMap = new LinkedHashMap<>();
globalPropertyViewMap.put(type.getName(), propertyViewMap);
}
return propertyViewMap;
}
private Map getClassDBNamePropertyMapForType(Class type) {
Map classDBNamePropertyMap = globalClassDBNamePropertyMap.get(type.getName());
if (classDBNamePropertyMap == null) {
classDBNamePropertyMap = new LinkedHashMap<>();
globalClassDBNamePropertyMap.put(type.getName(), classDBNamePropertyMap);
}
return classDBNamePropertyMap;
}
private Map getClassJSNamePropertyMapForType(Class type) {
Map classJSNamePropertyMap = globalClassJSNamePropertyMap.get(type.getName());
if (classJSNamePropertyMap == null) {
classJSNamePropertyMap = new LinkedHashMap<>();
globalClassJSNamePropertyMap.put(type.getName(), classJSNamePropertyMap);
}
return classJSNamePropertyMap;
}
private Map> getPropertyValidatorMapForType(Class type) {
Map> validatorMap = globalValidatorMap.get(type.getName());
if (validatorMap == null) {
validatorMap = new LinkedHashMap<>();
globalValidatorMap.put(type.getName(), validatorMap);
}
return validatorMap;
}
private Map getAggregatedPropertyGroupMapForType(Class type) {
Map groupMap = globalAggregatedPropertyGroupMap.get(type.getName());
if (groupMap == null) {
groupMap = new LinkedHashMap<>();
globalAggregatedPropertyGroupMap.put(type.getName(), groupMap);
}
return groupMap;
}
private Map getPropertyGroupMapForType(Class type) {
Map groupMap = globalPropertyGroupMap.get(type.getName());
if (groupMap == null) {
groupMap = new LinkedHashMap<>();
globalPropertyGroupMap.put(type.getName(), groupMap);
}
return groupMap;
}
private Set> getEntityCreationTransformationsForType(Class type) {
final String name = type.getName();
Set> transformations = globalTransformationMap.get(name);
if (transformations == null) {
transformations = new LinkedHashSet<>();
globalTransformationMap.put(name, transformations);
}
return transformations;
}
private Map getViewTransformationMapForType(Class type) {
Map viewTransformationMap = viewTransformations.get(type.getName());
if (viewTransformationMap == null) {
viewTransformationMap = new LinkedHashMap<>();
viewTransformations.put(type.getName(), viewTransformationMap);
}
return viewTransformationMap;
}
public void printCacheStats() {
System.out.println("###################################################");
System.out.println("" + relationshipEntityClassCache.size());
System.out.println("" + nodeEntityClassCache.size());
System.out.println("" + nodeEntityPackages.size());
System.out.println("" + relationshipPackages.size());
System.out.println("" + combinedTypeRelationClassCache.size());
System.out.println("" + interfaceCache.size());
System.out.println("" + globalPropertyViewMap.size());
System.out.println("" + globalValidatorMap.size());
System.out.println("" + globalClassDBNamePropertyMap.size());
System.out.println("" + globalClassJSNamePropertyMap.size());
System.out.println("" + globalAggregatedPropertyGroupMap.size());
System.out.println("" + globalPropertyGroupMap.size());
System.out.println("" + viewTransformations.size());
System.out.println("" + globalTransformationMap.size());
System.out.println("" + exportedMethodMap.size());
System.out.println("" + interfaceMap.size());
System.out.println("" + reverseInterfaceMap.size());
System.out.println("" + globalKnownPropertyKeys.size());
System.out.println("" + dynamicViews.size());
System.out.println("###################################################");
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy