Java tutorial
/** * Copyright (C) 2012 JBoss Inc * * 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.jboss.dashboard.factory; import org.apache.commons.jxpath.JXPathContext; import org.apache.commons.lang.StringUtils; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.*; /** * This class holds all the properties regarding a Factory bean definition. * Such definition is usually implemented by means of a .properties file inside the etc/factory directory.. */ public class Component { private static transient org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory .getLog(Component.class.getName()); public static final String ALIAS_PROPERTY = "alias"; public static final String CLASS_PROPERTY = "class"; public static final String SCOPE_PROPERTY = "scope"; public static final String DESC_PROPERTY = "description"; public static final String ENABLED_PROPERTY = "enabled"; public static final String SPECIAL_PROPERTY_PREFFIX = "$"; public static final String SCOPE_GLOBAL = "global"; public static final String SCOPE_SESSION = "session"; public static final String SCOPE_PANEL_SESSION = "panelSession"; public static final String SCOPE_REQUEST = "request"; public static final String SCOPE_VOLATILE = "volatile"; public final List VALID_SCOPES; public static final int STATUS_INCOMPLETE = 0; public static final int STATUS_VALID = 1; public static final int STATUS_INVALID = -1; private int status = STATUS_INCOMPLETE; private String name; private ComponentsTree tree; private String description; private String clazz; private String scope; private String alias; private boolean enabled = true; private Class componentClass; private Map componentConfiguredProperties = new Hashtable(); private List propertiesFilesAdded = new ArrayList(); private long creationOrderNumber; public long getCreationOrderNumber() { return creationOrderNumber; } public void setCreationOrderNumber(long creationOrderNumber) { this.creationOrderNumber = creationOrderNumber; } public Component(String name, ComponentsTree tree) { this.name = name; this.tree = tree; VALID_SCOPES = Collections.unmodifiableList(getInitScopes()); } public ComponentsTree getTree() { return tree; } protected List getInitScopes() { List l = new ArrayList(); l.add(SCOPE_GLOBAL); l.add(SCOPE_SESSION); l.add(SCOPE_PANEL_SESSION); l.add(SCOPE_REQUEST); l.add(SCOPE_VOLATILE); return l; } public Map getComponentConfiguredProperties() { return componentConfiguredProperties; } void addProperties(Properties properties, String fileName) { String declaredClass = properties.getProperty(SPECIAL_PROPERTY_PREFFIX + CLASS_PROPERTY); String declaredScope = properties.getProperty(SPECIAL_PROPERTY_PREFFIX + SCOPE_PROPERTY); String declaredDescr = properties.getProperty(SPECIAL_PROPERTY_PREFFIX + DESC_PROPERTY); String declaredEnabled = properties.getProperty(SPECIAL_PROPERTY_PREFFIX + ENABLED_PROPERTY); String declaredAlias = properties.getProperty(SPECIAL_PROPERTY_PREFFIX + ALIAS_PROPERTY); if (declaredClass != null && clazz != null) { try { Class currentClass = Class.forName(clazz); Class newClass = Class.forName(declaredClass); if (!currentClass.isAssignableFrom(newClass)) { log.warn("Assigning class " + declaredClass + " to component " + name + " may cause errors. " + "Default system class is " + clazz + ", new class is not descendant."); } } catch (ClassNotFoundException e) { log.error("Error:", e); } } clazz = declaredClass == null ? clazz : declaredClass; alias = declaredAlias == null ? alias : declaredAlias; scope = declaredScope == null ? scope : declaredScope; description = declaredDescr == null ? description : declaredDescr; enabled = declaredEnabled == null ? enabled : Boolean.valueOf(declaredEnabled).booleanValue(); for (Enumeration en = properties.propertyNames(); en.hasMoreElements();) { String propertyName = (String) en.nextElement(); if (propertyName.startsWith(SPECIAL_PROPERTY_PREFFIX)) { continue; } if (!isValidPropertyName(propertyName)) { log.error("Ignoring invalid property name " + propertyName + " in component " + name); continue; } String propertyValue = properties.getProperty(propertyName); storeProperty(propertyName, propertyValue); } propertiesFilesAdded.add(fileName); } /** * Determine if the property name is a valid property identifier * * @param propertyName Property name to evaluate * @return true if the property name is a valid property identifier */ protected boolean isValidPropertyName(String propertyName) { if (propertyName == null || propertyName.length() == 0) return false; if (propertyName.endsWith("-") || propertyName.endsWith("+")) propertyName = propertyName.substring(0, propertyName.length() - 1); if (propertyName.length() == 0) return false; for (int i = 0; i < propertyName.length(); i++) { char c = propertyName.charAt(i); if ('.' == c) break; //After this, it is a JXPath if (!Character.isJavaIdentifierPart(c)) return false; } return true; } /** * Store in componentProperties the property given by name and value. * * @param propertyName * @param propertyValue */ protected void storeProperty(String propertyName, String propertyValue) { List currentValue = null; PropertyChangeProcessingInstruction instruction = null; if (propertyName.endsWith("+")) { propertyName = propertyName.substring(0, propertyName.length() - 1).trim(); instruction = new PropertyAddProcessingInstruction(this, propertyName, propertyValue); } else if (propertyName.endsWith("-")) { propertyName = propertyName.substring(0, propertyName.length() - 1).trim(); instruction = new PropertySubstractProcessingInstruction(this, propertyName, propertyValue); } else { instruction = new PropertySetProcessingInstruction(this, propertyName, propertyValue); } currentValue = (List) componentConfiguredProperties.get(propertyName); if (currentValue == null) currentValue = new ArrayList(); currentValue.add(instruction); componentConfiguredProperties.put(propertyName, currentValue); } /** * Writes a property value to the component * * @param propertyName * @param propertyValues */ public void setProperty(String propertyName, String[] propertyValues) throws Exception { StringBuffer propertyValue = new StringBuffer(); for (int i = 0; i < propertyValues.length; i++) { String value = propertyValues[i]; if (i != 0) propertyValue.append(","); if (propertyValues.length > 1) propertyValue.append(StringUtils.replace(value, ",", ",,")); else propertyValue.append(value); } ArrayList list = new ArrayList(); list.add(new PropertySetProcessingInstruction(this, propertyName, propertyValue.toString())); setObjectProperty(getObject(), propertyName, list, null); } public void setProperty(String propertyName, File propertyValue) throws Exception { Object obj = getObject(); //Find a Field Field field = getField(obj, propertyName); if (field != null) { Class fieldClass = field.getType(); if (File.class.equals(fieldClass)) { if (log.isDebugEnabled()) log.debug("Invoking field " + propertyName + " with file."); field.set(obj, propertyValue); } else if (byte[].class.equals(fieldClass)) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); BufferedInputStream bis = new BufferedInputStream(new FileInputStream(propertyValue)); int byteRead = -1; while ((byteRead = bis.read()) != -1) { bos.write(byteRead); } bis.close(); bos.close(); if (log.isDebugEnabled()) log.debug("Invoking field " + propertyName + " with byte[]."); field.set(obj, bos.toByteArray()); } else { log.error("Cannot write a file to property " + propertyName + " in bean " + name); } return; } // Find a getter and setter. Method getter = getGetter(obj, propertyName); if (getter == null) { log.error("Cannot find a getter for property " + propertyName + " in class " + clazz + ". Ignoring property."); return; } Method setter = getSetter(obj, getter, propertyName); if (setter == null) { log.error("Cannot find a setter for property " + propertyName + " in class " + clazz + ". Ignoring property."); return; } Class returnType = getter.getReturnType(); if (File.class.equals(returnType)) { if (log.isDebugEnabled()) log.debug("Invoking " + setter + " with file."); setter.invoke(obj, new Object[] { propertyValue }); } else if (byte[].class.equals(returnType)) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); BufferedInputStream bis = new BufferedInputStream(new FileInputStream(propertyValue)); int byteRead = -1; while ((byteRead = bis.read()) != -1) { bos.write(byteRead); } bis.close(); bos.close(); if (log.isDebugEnabled()) log.debug("Invoking " + setter + " with byte[]."); setter.invoke(obj, new Object[] { bos.toByteArray() }); } else { log.error("Cannot write a file to property " + propertyName + " in bean " + name); } } public String getName() { return name; } public String getClazz() { return clazz; } public String getScope() { return scope; } public String getAlias() { return alias; } public String getDescription() { return description; } public List getPropertiesFilesAdded() { return Collections.unmodifiableList(propertiesFilesAdded); } public String toString() { StringBuffer sb = new StringBuffer(); sb.append(scope).append(" Component named ").append(name).append(" with class ").append(clazz) .append(" and params " + componentConfiguredProperties); return sb.toString(); } /** * Update the status attribute to reflect the component status */ public void validate() { if (!VALID_SCOPES.contains(scope)) { log.error("Invalid scope " + scope + " for component " + name + ""); status = STATUS_INVALID; return; } try { componentClass = Class.forName(clazz); } catch (Throwable e) { log.error("Invalid class for component " + name + ": " + clazz, e); status = STATUS_INVALID; return; } status = STATUS_VALID; } public int getStatus() { return status; } /** * @return The object represented in this component. */ public Object getObject() throws LookupException { if (!enabled) return null; if (log.isDebugEnabled()) log.debug("Getting " + scope + " component " + name); if (status != STATUS_INVALID) { LookupHelper helper = ComponentsContextManager.getLookupHelper(); synchronized (helper.getSynchronizationObject(getScope())) { if (getTheInstance() == null) { return makeAndSetNewInstance(); } } return getTheInstance(); } else { log.warn("Component " + name + " is in invalid status."); } return null; } protected final Object getTheInstance() throws LookupException { LookupHelper helper = ComponentsContextManager.getLookupHelper(); if (helper != null) { return helper.lookupObject(scope, name); } else { throw new LookupException( "Cannot get " + getName() + ". Factory operation is outside a valid context."); } } protected final void setTheInstance(Object instance) { LookupHelper helper = ComponentsContextManager.getLookupHelper(); if (helper != null) { helper.storeObject(scope, name, instance); } else { log.error("Cannot set " + getName() + ". Factory operation is outside a valid context."); } } protected Object makeAndSetNewInstance() { LookupHelper helper = ComponentsContextManager.getLookupHelper(); synchronized (helper.getSynchronizationObject(getScope())) { Object object = null; if (status == STATUS_VALID) { if (log.isDebugEnabled()) log.debug("Making and initializing new " + clazz); if (scope.equals(SCOPE_GLOBAL)) { try { object = componentClass.getMethod("getInstance", new Class[0]).invoke(null, new Object[0]); } catch (Exception e) { if (log.isDebugEnabled()) log.debug("Error using getInstance() method on " + clazz); } } if (object == null) try { object = componentClass.getConstructor(new Class[0]).newInstance(new Object[0]); } catch (Exception e) { log.error("Error creating instance for component " + name + " :", e); } //End. Object should be created now if (object == null) { status = STATUS_INVALID; log.error("Make sure the component class has a default public constructor" + (scope.equals(SCOPE_GLOBAL) ? ", or a static getInstance() method." : ".")); } else { if (object instanceof FactoryLifecycle) { try { if (object instanceof BasicFactoryElement) { ((BasicFactoryElement) object).setComponentName(name); ((BasicFactoryElement) object).setComponentScope(scope); ((BasicFactoryElement) object).setComponentDescription(description); ((BasicFactoryElement) object).setComponentAlias(alias); } ((FactoryLifecycle) object).init(); ((FactoryLifecycle) object).stop(); } catch (Exception e) { log.error("Error in component lifecycle ", e); } } setTheInstance(object); JXPathContext ctx = JXPathContext.newContext(object); for (Iterator it = componentConfiguredProperties.keySet().iterator(); it.hasNext();) { String propertyName = (String) it.next(); List propertyValue = (List) componentConfiguredProperties.get(propertyName); try { setObjectProperty(object, propertyName, propertyValue, ctx); } catch (Exception e) { log.error("Error. Cannot set property " + getName() + "." + propertyName + " with configured values " + propertyValue, e); } } status = STATUS_VALID; if (object instanceof FactoryLifecycle) { try { ((FactoryLifecycle) object).start(); } catch (Exception e) { log.error("Error in component lifecycle ", e); } } setCreationOrderNumber(getTree().getNewOrderCounter()); } } else { log.error("Infinite loop detected. Caused by component " + name + " or some other component used by it."); } return object; } } protected Object setObjectProperty(Object obj, String propertyName, List propertyValue, JXPathContext ctx) throws Exception { if (log.isDebugEnabled()) log.debug("Setting in " + clazz + " property " + propertyName + " with values " + propertyValue); if (obj instanceof Map) { ((Map) obj).put(propertyName, getValueForProperty(propertyValue, String.class)); //Map of Strings } else if (obj instanceof List) { try { int index = Integer.parseInt(propertyName); while (((List) obj).size() <= index) ((List) obj).add(null); ((List) obj).set(index, getValueForProperty(propertyValue, String.class)); //List of Strings } catch (Exception e) { log.error("Error setting position " + propertyName + " in List " + name + ". ", e); } } else if (propertyName.indexOf('.') != -1) { //Set it by JXPath, valid only for strings ctx = ctx != null ? ctx : JXPathContext.newContext(obj); ctx.setValue(propertyName.replace('.', '/'), getValueForProperty(propertyValue, String.class)); } else { //Find a Field Field field = getField(obj, propertyName); if (field != null) { Class fieldClass = field.getType(); Object value = getValueForProperty(propertyValue, fieldClass); field.set(obj, value); return obj; } // Find a getter and setter. Method getter = getGetter(obj, propertyName); if (getter == null) { log.error("Cannot find a getter for property " + propertyName + " in class " + clazz + ". Ignoring property."); return obj; } Method setter = getSetter(obj, getter, propertyName); if (setter == null) { log.error("Cannot find a setter for property " + propertyName + " in class " + clazz + ". Ignoring property."); return obj; } Object value = getValueForProperty(propertyValue, getter.getReturnType()); if (log.isDebugEnabled()) log.debug("Invoking " + setter + " with value=" + value); setter.invoke(obj, new Object[] { value }); } return obj; } protected Field getField(Object obj, String propertyName) { try { Field field = obj.getClass().getField(propertyName); int modifiers = field.getModifiers(); if (Modifier.isPublic(modifiers) && !Modifier.isFinal(field.getModifiers())) { return field; } } catch (NoSuchFieldException e) { } if (log.isDebugEnabled()) { log.debug("Could not find a field for property " + propertyName + " in " + obj.getClass()); } return null; } protected Method getGetter(Object obj, String propertyName) { Method getter = null; String propertyAccessorSuffix = StringUtils.capitalize(propertyName); String getterName = "get" + propertyAccessorSuffix; try { getter = obj.getClass().getMethod(getterName, new Class[0]); } catch (NoSuchMethodException e) { log.debug("No getter " + getterName + " found."); String booleanGetterName = "is" + propertyAccessorSuffix; try { getter = obj.getClass().getMethod(booleanGetterName, new Class[0]); } catch (NoSuchMethodException e1) { log.debug("No getter " + booleanGetterName + " found."); } } return getter; } protected Method getSetter(Object obj, Method getter, String propertyName) { Method setter = null; String propertyAccessorSuffix = StringUtils.capitalize(propertyName); String setterName = "set" + propertyAccessorSuffix; Class returnType = getter.getReturnType(); try { setter = obj.getClass().getMethod(setterName, new Class[] { returnType }); } catch (NoSuchMethodException e) { log.error("Cannot find a setter for property " + propertyName + " in class " + clazz + ". Ignoring property."); } return setter; } protected Object getValueForProperty(List values, Class expectedClass) throws Exception { if (log.isDebugEnabled()) log.debug("Converting to " + expectedClass + " values " + values); if (values == null || values.isEmpty()) return null; Object valueToReturn = null; for (int i = 0; i < values.size(); i++) { PropertyChangeProcessingInstruction instruction = (PropertyChangeProcessingInstruction) values.get(i); valueToReturn = instruction.getValueAfterChange(valueToReturn, expectedClass); } if (expectedClass.isArray() && expectedClass.getComponentType().isPrimitive() && valueToReturn != null) { //Convert arrays of Integers, Shorts, ... to primitive type arrays. if (expectedClass.getComponentType().equals(int.class)) { int[] newValueToReturn = new int[((Object[]) valueToReturn).length]; for (int i = 0; i < newValueToReturn.length; newValueToReturn[i] = ((Integer) ((Object[]) valueToReturn)[i++]) .intValue()) ; return newValueToReturn; } else if (expectedClass.getComponentType().equals(boolean.class)) { boolean[] newValueToReturn = new boolean[((Object[]) valueToReturn).length]; for (int i = 0; i < newValueToReturn.length; newValueToReturn[i] = ((Boolean) ((Object[]) valueToReturn)[i++]) .booleanValue()) ; return newValueToReturn; } else if (expectedClass.getComponentType().equals(long.class)) { long[] newValueToReturn = new long[((Object[]) valueToReturn).length]; for (int i = 0; i < newValueToReturn.length; newValueToReturn[i] = ((Long) ((Object[]) valueToReturn)[i++]) .longValue()) ; return newValueToReturn; } else if (expectedClass.getComponentType().equals(char.class)) { char[] newValueToReturn = new char[((Object[]) valueToReturn).length]; for (int i = 0; i < newValueToReturn.length; newValueToReturn[i] = ((Character) ((Object[]) valueToReturn)[i++]) .charValue()) ; return newValueToReturn; } else if (expectedClass.getComponentType().equals(double.class)) { double[] newValueToReturn = new double[((Object[]) valueToReturn).length]; for (int i = 0; i < newValueToReturn.length; newValueToReturn[i] = ((Double) ((Object[]) valueToReturn)[i++]) .doubleValue()) ; return newValueToReturn; } else if (expectedClass.getComponentType().equals(float.class)) { float[] newValueToReturn = new float[((Object[]) valueToReturn).length]; for (int i = 0; i < newValueToReturn.length; newValueToReturn[i] = ((Float) ((Object[]) valueToReturn)[i++]) .floatValue()) ; return newValueToReturn; } else if (expectedClass.getComponentType().equals(byte.class)) { byte[] newValueToReturn = new byte[((Object[]) valueToReturn).length]; for (int i = 0; i < newValueToReturn.length; newValueToReturn[i] = ((Byte) ((Object[]) valueToReturn)[i++]) .byteValue()) ; return newValueToReturn; } else if (expectedClass.getComponentType().equals(short.class)) { short[] newValueToReturn = new short[((Object[]) valueToReturn).length]; for (int i = 0; i < newValueToReturn.length; newValueToReturn[i] = ((Short) ((Object[]) valueToReturn)[i++]) .shortValue()) ; return newValueToReturn; } } return valueToReturn; } }