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

net.officefloor.web.value.load.ValueLoaderSource Maven / Gradle / Ivy

There is a newer version: 3.40.0
Show newest version
/*
 * OfficeFloor - http://www.officefloor.net
 * Copyright (C) 2005-2018 Daniel Sagenschneider
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see .
 */
package net.officefloor.web.value.load;

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import net.officefloor.server.http.HttpException;
import net.officefloor.web.HttpContentParameter;
import net.officefloor.web.HttpCookieParameter;
import net.officefloor.web.HttpHeaderParameter;
import net.officefloor.web.HttpPathParameter;
import net.officefloor.web.HttpQueryParameter;
import net.officefloor.web.build.HttpValueLocation;

/**
 * Sources the {@link ValueLoaderFactory} for a {@link Class} type.
 * 
 * @author Daniel Sagenschneider
 */
public class ValueLoaderSource {

	/**
	 * Loads the value onto the object.
	 * 
	 * @param object
	 *            {@link Object} to have value loaded on it.
	 * @param method
	 *            {@link Method} to load the values.
	 * @param parameters
	 *            Values to be loaded into the {@link Method}.
	 * @throws HttpException
	 *             If fails to load the values.
	 */
	public static void loadValue(Object object, Method method, Object... parameters) throws HttpException {
		try {

			// Load the value
			method.invoke(object, parameters);

		} catch (InvocationTargetException ex) {
			// Propagate cause of invocation escalation
			Throwable cause = ex.getCause();
			if (cause instanceof HttpException) {
				throw (HttpException) cause;
			} else {
				throw new HttpException(cause);
			}
		} catch (Exception ex) {
			throw new HttpException(ex);
		}
	}

	/**
	 * Obtains the {@link HttpValueLocation} for the {@link Method}.
	 * 
	 * @param method
	 *            {@link Method}.
	 * @return {@link HttpValueLocation} or null if match all
	 *         {@link HttpValueLocation}.
	 */
	public static HttpValueLocation getLocation(Method method) {

		// Interrogate for annotations
		HttpValueLocation location = extractLocation(method);
		if (location != null) {
			return location; // defined on method
		}

		// Determine if associated field for property
		String methodName = method.getName();
		String propertyName = methodName.substring("set".length());
		propertyName = propertyName.substring(0, 1).toLowerCase() + propertyName.substring(1);
		Class clazz = method.getDeclaringClass();
		while (clazz != null) {
			for (Field field : clazz.getDeclaredFields()) {
				if (propertyName.equals(field.getName())) {
					// Found field, so return based location details
					return extractLocation(field);
				}
			}
			clazz = clazz.getSuperclass();
		}

		// As here, matches any location
		return null;
	}

	/**
	 * Extracts the {@link HttpValueLocation} from the {@link AnnotatedElement}.
	 * 
	 * @param element
	 *            {@link AnnotatedElement}.
	 * @return Extracted {@link HttpValueLocation} or null if not
	 *         {@link HttpValueLocation} details.
	 */
	private static HttpValueLocation extractLocation(AnnotatedElement element) {
		if (element.isAnnotationPresent(HttpPathParameter.class)) {
			return HttpValueLocation.PATH;
		} else if (element.isAnnotationPresent(HttpQueryParameter.class)) {
			return HttpValueLocation.QUERY;
		} else if (element.isAnnotationPresent(HttpHeaderParameter.class)) {
			return HttpValueLocation.HEADER;
		} else if (element.isAnnotationPresent(HttpCookieParameter.class)) {
			return HttpValueLocation.COOKIE;
		} else if (element.isAnnotationPresent(HttpContentParameter.class)) {
			return HttpValueLocation.ENTITY;
		} else {
			return null;
		}
	}

	/**
	 * Indicates if match {@link HttpValueLocation}.
	 * 
	 * @param loaderLocation
	 *            {@link StatelessValueLoader} {@link HttpValueLocation}. May be
	 *            null to match any {@link HttpValueLocation}.
	 * @param valueLocation
	 *            {@link HttpValueLocation} for the value.
	 * @return true if match {@link HttpValueLocation}.
	 */
	public static boolean isLocationMatch(HttpValueLocation loaderLocation, HttpValueLocation valueLocation) {
		return (loaderLocation == null) || (loaderLocation == valueLocation);
	}

	/**
	 * Creates {@link StatelessValueLoaderFactory} instances for the type.
	 * 
	 * @param type
	 *            Type to create {@link StatelessValueLoaderFactory} instances.
	 * @param propertyKeyFactory
	 *            {@link PropertyKeyFactory}.
	 * @param objectInstantiator
	 *            {@link ObjectInstantiator}.
	 * @param aliasMappings
	 *            Alias mappings.
	 * @param factoriesByType
	 *            {@link StatelessValueLoaderFactory} instances by type.
	 * @return {@link StatelessValueLoaderFactory} instances for the type.
	 * @throws Exception
	 *             If fails to create the {@link StatelessValueLoaderFactory}
	 *             instances.
	 */
	public static StatelessValueLoaderFactory[] createValueLoaderFactories(Class type,
			PropertyKeyFactory propertyKeyFactory, ObjectInstantiator objectInstantiator,
			Map aliasMappings, Map, StatelessValueLoaderFactory[]> factoriesByType)
			throws Exception {

		// Determine if already have factories for the type
		StatelessValueLoaderFactory[] factories = factoriesByType.get(type);
		if (factories != null) {
			return factories;
		}

		// Extract the value loader meta-data
		List valueLoaders = new ArrayList();
		NEXT_METHOD: for (Method method : type.getMethods()) {

			// Ensure a public void method
			if (!Modifier.isPublic(method.getModifiers())) {
				continue NEXT_METHOD;
			}
			if ((method.getReturnType() != null) && (method.getReturnType() != Void.TYPE)) {
				continue NEXT_METHOD;
			}

			// Ensure the method begins with 'set'
			String methodName = method.getName();
			final String SETTER_PREFIX = "set";
			if (!methodName.startsWith(SETTER_PREFIX)) {
				continue NEXT_METHOD;
			}

			// Ensure there is a property name (something after 'set')
			String propertyName = methodName.substring(SETTER_PREFIX.length());
			if ((propertyName == null) || (propertyName.length() == 0)) {
				continue NEXT_METHOD;
			}

			// Ensure the appropriate method parameters
			Class[] parameterTypes = method.getParameterTypes();
			switch (parameterTypes.length) {
			case 1:
				// Single parameter
				Class objectType = parameterTypes[0];
				if (objectType.isPrimitive()) {
					continue NEXT_METHOD; // ignore loading primitives
				}
				break;

			case 2:
				// Register for keyed values

				// Ensure first parameter is a String
				if (!String.class.isAssignableFrom(parameterTypes[0])) {
					continue NEXT_METHOD; // must be String
				}

				// Ensure second parameter is not a primative
				objectType = parameterTypes[1];
				if (objectType.isPrimitive()) {
					continue NEXT_METHOD; // ignore loading primitives
				}
				break;

			default:
				// Ignore method as not loader
				continue NEXT_METHOD;
			}

			// Add the value loader for the property
			valueLoaders.add(new ValueLoaderStruct(method, propertyName));

			// Register the aliases
			for (String aliasName : aliasMappings.keySet()) {
				String propertyAlias = aliasMappings.get(aliasName);

				// Determine if alias for the property
				PropertyKey propertyKey = propertyKeyFactory.createPropertyKey(propertyName);
				PropertyKey aliasKey = propertyKeyFactory.createPropertyKey(propertyAlias);
				if (!propertyKey.equals(aliasKey)) {
					continue; // not alias for property
				}

				// Add the alias for the property
				valueLoaders.add(new ValueLoaderStruct(method, aliasName));
			}
		}

		// Create and register the value loader factories array.
		// Must be registered before loading factories due to recursive types.
		factories = new StatelessValueLoaderFactory[valueLoaders.size()];
		factoriesByType.put(type, factories);

		// Create and load factories onto the array
		List objectParameters = new LinkedList();
		for (int i = 0; i < factories.length; i++) {
			ValueLoaderStruct struct = valueLoaders.get(i);

			// Obtain the method details
			String methodName = struct.method.getName();
			Class[] parameterTypes = struct.method.getParameterTypes();

			// Obtain the property name
			String propertyName = struct.propertyName;

			// Register the appropriate value loader meta-data
			StatelessValueLoaderFactory factory = null;
			switch (parameterTypes.length) {
			case 1:
				// Register for single parameter
				Class objectType = parameterTypes[0];
				if (String.class.isAssignableFrom(objectType)) {
					// String loader
					factory = new SingleParameterValueLoaderFactory(propertyName, methodName);
				} else {
					// Create the value loader for loaded object
					StatelessValueLoaderFactory[] objectTypeFactories = createValueLoaderFactories(objectType,
							propertyKeyFactory, objectInstantiator, aliasMappings, factoriesByType);

					// Object loader
					ObjectParameterValueLoaderFactory objectParameter = new ObjectParameterValueLoaderFactory(
							propertyName, methodName, objectType, objectInstantiator, propertyKeyFactory);
					factory = objectParameter;

					// Add object parameter
					objectParameters.add(new ObjectParameterStruct(objectType, objectTypeFactories, propertyKeyFactory,
							objectParameter, null));
				}
				break;

			case 2:
				// Register for keyed values
				objectType = parameterTypes[1];
				if (String.class.isAssignableFrom(objectType)) {
					// Keyed string loader
					factory = new KeyedParameterValueLoaderFactory(propertyName, methodName);

				} else {
					// Create the value loader for loaded object
					StatelessValueLoaderFactory[] objectTypeFactories = createValueLoaderFactories(objectType,
							propertyKeyFactory, objectInstantiator, aliasMappings, factoriesByType);

					// Keyed object loader
					KeyedObjectValueLoaderFactory keyedObject = new KeyedObjectValueLoaderFactory(propertyName,
							methodName, objectType, objectInstantiator, propertyKeyFactory);
					factory = keyedObject;

					// Add object parameter
					objectParameters.add(new ObjectParameterStruct(objectType, objectTypeFactories, propertyKeyFactory,
							null, keyedObject));
				}
				break;
			}

			// Load the factory
			factories[i] = factory;
		}

		// Initialise the object parameters
		for (ObjectParameterStruct objectParameter : objectParameters) {
			objectParameter.init();
		}

		// Return the factories
		return factories;
	}

	/**
	 * Creates the {@link StatelessValueLoader}.
	 * 
	 * @param clazz
	 *            {@link Class} to create the specific {@link StatelessValueLoader}.
	 * @param factories
	 *            {@link StatelessValueLoaderFactory} instances for the
	 *            {@link StatelessValueLoader}.
	 * @param propertyKeyFactory
	 *            {@link PropertyKeyFactory}.
	 * @return {@link StatelessValueLoader}.
	 * @throws Exception
	 *             If fails to create the {@link StatelessValueLoader}.
	 */
	private static StatelessValueLoader createValueLoader(Class clazz, StatelessValueLoaderFactory[] factories,
			PropertyKeyFactory propertyKeyFactory) throws Exception {

		// Create the value loaders for the class
		Map valueLoaders = new HashMap();
		for (StatelessValueLoaderFactory factory : factories) {
			String propertyName = factory.getPropertyName();
			StatelessValueLoader valueLoader = factory.createValueLoader(clazz);
			valueLoaders.put(propertyKeyFactory.createPropertyKey(propertyName), valueLoader);
		}

		// Return the root value loader
		return new RootStatelessValueLoader(valueLoaders, propertyKeyFactory);
	}

	/**
	 * {@link StatelessValueLoaderFactory} instances.
	 */
	private StatelessValueLoaderFactory[] factories;

	/**
	 * {@link PropertyKeyFactory}.
	 */
	private PropertyKeyFactory propertyKeyFactory;

	/**
	 * Initialise.
	 * 
	 * @param type
	 *            Type to interrogate for properties to be loaded.
	 * @param isCaseInsensitive
	 *            Flag indicating if case sensitive matching.
	 * @param aliasMappings
	 *            Alias Mappings.
	 * @param objectInstantiator
	 *            {@link ObjectInstantiator}.
	 * @throws Exception
	 *             If fails to initialise.
	 */
	public ValueLoaderSource(Class type, boolean isCaseInsensitive, Map aliasMappings,
			ObjectInstantiator objectInstantiator) throws Exception {

		// Create the property key factory
		this.propertyKeyFactory = new PropertyKeyFactory(isCaseInsensitive);

		// Load the factories for the type
		this.factories = createValueLoaderFactories(type, this.propertyKeyFactory, objectInstantiator, aliasMappings,
				new HashMap, StatelessValueLoaderFactory[]>());
	}

	/**
	 * 

* Sources the {@link ValueLoaderFactory}. *

* The returned {@link ValueLoaderFactory} is specific to the input * {@link Class}. The {@link ValueLoaderFactory} will not work with children of * the {@link Class}. A new {@link ValueLoaderFactory} must be created for each * child type. * * @param * {@link Class} type. * @param clazz * {@link Class} for a dedicated {@link ValueLoaderFactory}. It is * expected that all property methods on the type are on * this clazz. * @return {@link ValueLoaderFactory}. * @throws Exception * If fails to source the {@link ValueLoaderFactory}. */ public ValueLoaderFactory sourceValueLoaderFactory(Class clazz) throws Exception { // Create and return the value loader factory for the class return new ValueLoaderFactoryImpl(createValueLoader(clazz, this.factories, this.propertyKeyFactory)); } /** * Struct containing details for the {@link StatelessValueLoaderFactory}. */ private static class ValueLoaderStruct { /** * {@link Method}. */ public final Method method; /** * Property name. */ public final String propertyName; /** * Initiate. * * @param method * {@link Method}. * @param propertyName * Property name. */ public ValueLoaderStruct(Method method, String propertyName) { this.method = method; this.propertyName = propertyName; } } /** * Struct containing details for the {@link ObjectParameterValueLoaderFactory} * and {@link KeyedObjectValueLoaderFactory}. */ private static class ObjectParameterStruct { /** * Type of the object being loaded. */ public final Class type; /** * {@link StatelessValueLoaderFactory} instances. */ public final StatelessValueLoaderFactory[] factories; /** * {@link PropertyKeyFactory}. */ public final PropertyKeyFactory propertyKeyFactory; /** * {@link ObjectParameterValueLoaderFactory}. */ public final ObjectParameterValueLoaderFactory parameter; /** * {@link KeyedObjectValueLoaderFactory}. */ public final KeyedObjectValueLoaderFactory keyed; /** * Initiate. * * @param type * Type of the object being loaded. * @param factories * {@link StatelessValueLoaderFactory} instances. * @param propertyKeyFactory * {@link PropertyKeyFactory}. * @param parameter * {@link ObjectParameterValueLoaderFactory}. * @param keyed * {@link KeyedObjectValueLoaderFactory}. */ public ObjectParameterStruct(Class type, StatelessValueLoaderFactory[] factories, PropertyKeyFactory propertyKeyFactory, ObjectParameterValueLoaderFactory parameter, KeyedObjectValueLoaderFactory keyed) { this.type = type; this.factories = factories; this.propertyKeyFactory = propertyKeyFactory; this.parameter = parameter; this.keyed = keyed; } /** * Initialise the object parameter. * * @throws Exception * If fails to initialise. */ public void init() throws Exception { // Create the value loader StatelessValueLoader valueLoader = createValueLoader(this.type, this.factories, this.propertyKeyFactory); // Load into object parameter if (this.parameter != null) { this.parameter.setValueLoader(valueLoader); } else { this.keyed.setValueLoader(valueLoader); } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy