/** * Copyright 2011 meltmedia * * 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 org.xchain.framework.util; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import org.apache.commons.jxpath.JXPathContext; import org.xchain.annotations.Begin; import org.xchain.annotations.Component; import org.xchain.annotations.End; import org.xchain.annotations.In; import org.xchain.annotations.PrefixMapping; import org.xchain.framework.jxpath.ScopedJXPathContextImpl; import org.xchain.framework.lifecycle.ComponentAnalysis; import org.xchain.framework.lifecycle.ComponentAnalysis.InjectionAnalysis; /** * Utility class for Components. * * @author Devon Tackett * @author John Trimble */ public class ComponentUtil { /** * Perform an analysis of a component class. * * @param componentClass The component class to analyze. * * @return An analysis of the component class. */ public static ComponentAnalysis createAnalysis(Class componentClass) { ComponentAnalysis analysis = new ComponentAnalysis(componentClass); // Get the component annotation Component componentAnnotation = (Component)componentClass.getAnnotation(Component.class); // Get the localname and scope. analysis.setLocalName(componentAnnotation.localName()); analysis.setScope(componentAnnotation.scope()); // Get the method based dependency injections. This will only work with public methods on the class. for (Method method : componentClass.getMethods()) { In inAnnotation = method.getAnnotation(In.class); if (inAnnotation != null) { analysis.addInjection(new InjectionAnalysis(inAnnotation.select(), inAnnotation.prefixMappings(), inAnnotation.nullable(), method)); } } // Build field based dependency injections. analyzeFields(componentClass, analysis); return analysis; } /** * Gather field based dependency injections. This will find any private, protected or public fields with dependency injections. * * @param componentClass The component class to analyze. * @param analysis The analysis to build upon. */ private static void analyzeFields(Class componentClass, ComponentAnalysis analysis) { for (Field field : componentClass.getDeclaredFields()) { In inAnnotation = field.getAnnotation(In.class); if (inAnnotation != null) { field.setAccessible(true); analysis.addInjection(new InjectionAnalysis(inAnnotation.select(), inAnnotation.prefixMappings(), inAnnotation.nullable(), field)); } } // Check any parent interfaces. for (Class parentClass : componentClass.getInterfaces()) { analyzeFields(parentClass, analysis); } // Check the super class. if (componentClass.getSuperclass() != null) { analyzeFields(componentClass.getSuperclass(), analysis); } } /** * Create a new component from the given analysis. This does not perform dependency injection nor begin the component. */ public static Object createComponent(ComponentAnalysis analysis) throws InstantiationException, IllegalAccessException { return analysis.getComponentClass().newInstance(); } /** * Perform dependency injection on the given component. */ public static void doInjection(Object component, ComponentAnalysis analysis, JXPathContext context) throws IllegalAccessException, InvocationTargetException { for (InjectionAnalysis injection : analysis.getInjections()) { Map<String, String> originalPrefixMapping = pushPrefixMap(context, injection.getPrefixMappings()); try { if (injection.getField() != null) { injection.getField().set(component, context.getValue(injection.getSelect())); } else if (injection.getMethod() != null){ injection.getMethod().invoke(component, context.getValue(injection.getSelect())); } } catch (Exception ex) { if (!injection.isNullable()) { // Unable to inject value on a non-nullable field. String failurePoint = null; if (injection.getField() != null) failurePoint = "field '" + injection.getField().getName() + "'"; else if (injection.getMethod() != null) failurePoint = "method '" + injection.getMethod().getName() + "'"; String failurePath = " from path '" + injection.getSelect() + "'"; if( context instanceof ScopedJXPathContextImpl ) { ScopedJXPathContextImpl impl = (ScopedJXPathContextImpl)context; failurePath += " at scope '" + impl.getScope() + "'."; } throw new DependencyInjectionException("Unable to inject value into " + failurePoint + failurePath, ex); } } popPrefixMap(context, originalPrefixMapping); } } /** * Add the given array of prefix mappings to the context. * * @param context The working context. * @param prefixMappings The prefix mappings to add. * * @return A prefix to namespace uri mapping of prefixes that were replaced. */ private static Map<String, String> pushPrefixMap(JXPathContext context, PrefixMapping prefixMappings[]) { Map<String, String> originalPrefixMapping = null; if (prefixMappings.length > 0) { originalPrefixMapping = new HashMap<String, String>(); // Perform prefix mappings for (PrefixMapping prefixMap : prefixMappings) { // Store the original namespace for the prefix. originalPrefixMapping.put(prefixMap.prefix(), context.getNamespaceURI(prefixMap.prefix())); // Register the new namespace. context.registerNamespace(prefixMap.prefix(), prefixMap.uri()); } } return originalPrefixMapping; } /** * Restore the given prefix to namespace uri mapping onto the context. * * @param context The working context. * @param originalPrefixMapping The mapping to restore. */ private static void popPrefixMap(JXPathContext context, Map<String, String> originalPrefixMapping) { if (originalPrefixMapping != null) { // Reverse prefix mappings for (String prefix : originalPrefixMapping.keySet()) { // Restore the original namespace for the prefix. context.registerNamespace(prefix, originalPrefixMapping.get(prefix)); } } } /** * Invoke the begin method on the given component. */ public static void doBegin(Object component) throws InvocationTargetException, IllegalAccessException { for (Method method : component.getClass().getMethods()) { Begin beginAnnotation = method.getAnnotation(Begin.class); if (beginAnnotation != null) { method.invoke(component); } } } /** * Invoke the end method on the given component. */ public static void doEnd(Object component) throws InvocationTargetException, IllegalAccessException { for (Method method : component.getClass().getMethods()) { End beginAnnotation = method.getAnnotation(End.class); if (beginAnnotation != null) { method.invoke(component); } } } }