All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.structr.module.JarConfigurationProvider Maven / Gradle / Ivy

Go to download

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 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 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 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