Package org.jbpm.wire.descriptor

Source Code of org.jbpm.wire.descriptor.ObjectDescriptor

package org.jbpm.wire.descriptor;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

import org.jbpm.PvmException;
import org.jbpm.util.ArrayUtil;
import org.jbpm.util.JbpmReflectException;
import org.jbpm.util.ReflectUtil;
import org.jbpm.wire.Descriptor;
import org.jbpm.wire.WireContext;
import org.jbpm.wire.WireDefinition;
import org.jbpm.wire.WireException;
import org.jbpm.wire.operation.FieldOperation;
import org.jbpm.wire.operation.Operation;
import org.jbpm.wire.operation.PropertyOperation;

/**
* <p>This {@link Descriptor} creates and initializes an object.
* Objects can be instantiated from a constructor or from a method invocation.</p>
*
* <p>The way to create an object is specified one of these methods (see <a href='#create'>creating objects</a>):
* <ul>
* <li>className ({@link #setClassName(String)})</li>
* <li>factoryObjectName ({@link #setFactoryObjectName(String)})</li>
* <li>factoryDescriptor ({@link #setFactoryDescriptor(Descriptor)})</li>
* </ul>
* Only one of these methods can be used.
* </p>
*
* <h3 id='create'>Creating objects</h3>
* <h4>Creating object from a constructor</h4>
*
* <p>This method is used when <code>{@link #getClassName()}!=null && {@link #getMethodName()}==null</code>.</p>
*
* <p>The {@link #construct(WireContext)} method creates a new object
* from a constructor matching the given arguments
* (specified with {@link #setArgDescriptors(List)}).</p>
*
*
* <h4>Creating an object from a method invocation</h4>
*
* <p>The name of the method to call is specified by the method attribute.</p>
* <ul>
* <li>If the method is <i>static</i>, the related class is {@link #getClassName()}.</li>
* <li>If the method is an object method, the object to which the method will be applied is defined by:
<ul>
*    <li>If <code>{@link #getFactoryObjectName()}!=null</code>: the object with the name factoryObjectName will be fetched from the context.</li>
*    <li>if <code>{@link #getFactoryDescriptor()}!=null</code>: the object will be created from the factory descriptor.</li>
</ul>
* </li>
* </ul>
* <p>The object returned by {@link #construct(WireContext)} is the object returned by the method invocation.</p>
*
*
* <h3>Initializing Objects</h3>
* <h4>Auto Wiring</h4>
* <p>If the auto wiring is enabled for the object (<code>{@link #isAutoWireEnabled()}==true</code>),
* the WireContext will try to look for objects with the same name as the fields in the class.
* If it finds an object with that name, and if it is assignable to the field's type, it is automatically injected,
* without the need for explicit {@link FieldOperation} that specifies the injection in the wiring xml.</p>
* <p>If the auto wiring is enabled and the WireContext finds an object with the name of a field, but not assignable to this field,
* a warning message is generated.</p>
*
* <p>Auto-wiring is disabled by default.</p>
*
* <h4>Operations</h4>
* <p>Field injection or property injection are done after the auto-wiring. For more information, see {@link Operation}.</p>
*
* <p>If a field was injected by auto-wiring, its value can be overridden by specifying
*  a {@link FieldOperation} or {@link PropertyOperation} operation.</p>
*
* @author Tom Baeyens
* @author Guillaume Porcher (documentation)
*
*/
public class ObjectDescriptor extends AbstractDescriptor implements Descriptor {

  private static final long serialVersionUID = 1L;
  private static Logger log = Logger.getLogger(ObjectDescriptor.class.getName());

  String className = null;

  /** specifies the object reference on which the method will be invoked.
   * Either className, objectName or a descriptor has to be specified.
   *
   * TODO check if this member can be replaced by a RefDescriptor in the factoryDescriptor member.
   *
   * */
  String factoryObjectName = null;

  /** specifies the object on which to invoke the method.
   * Either className, objectName or a descriptor has to be specified. */
  Descriptor factoryDescriptor = null;

  String methodName = null;

  /** map to db as a component */
  List<ArgDescriptor> argDescriptors = null;
  /** list of operations to perform during initialization. */
  List<Operation> operations = null;

  /** True if autowiring is enabled.  */
  boolean isAutoWireEnabled = false;

  /**
   * This method constructs a new Object from the ObjectDefinition.
   * This object will be created from a class constructor or from a method invocation.
   * @throws WireException one of the following exception occurred:
   * <ul><li>if the object cannot be created (unable to load the specified class or no matching constructor found)</li>
   * <li>if the invocation of the specified method failed</li>
   * </ul>
   * @see ObjectDescriptor
   */
  public Object construct(WireContext wireContext) {
    Object object = null;
    Class<?> clazz = null;

    if (className!=null) {
      try {
        ClassLoader classLoader = wireContext.getClassLoader();
        clazz = ReflectUtil.loadClass(classLoader, className);
      } catch (Exception e) {
        throw new WireException("couldn't create object"+(name!=null ? " '"+name+"'" : "")+": "+e.getMessage(), e);
      }

      if (methodName==null) {
        // plain instantiation
        try {
          Object[] args = getArgs(wireContext, argDescriptors);
          Constructor<?> constructor = ReflectUtil.findConstructor(clazz, argDescriptors, args);
          if (constructor==null) {
            throw new WireException("couldn't find constructor "+clazz.getName()+" with args "+ArrayUtil.toString(args));
          }
          object = constructor.newInstance(args);
        } catch (WireException e) {
          throw e;
        } catch (Exception e) {
          throw new WireException("couldn't create object '"+(name!=null ? name : className)+"': "+e.getMessage(), e);
        }
      }

    } else if (factoryObjectName!=null) {
      // referenced factory object
      object = wireContext.get(factoryObjectName, false);
      if (object==null) {
        throw new WireException("can't invoke method '"+methodName+"' on null, resulted from fetching object '"+factoryObjectName+"' from this wiring environment");
      }

    } else if (factoryDescriptor!=null) {
      // factory object descriptor
      object = wireContext.create(factoryDescriptor, false);
      if (object==null) {
        throw new WireException("created factory object is null, can't invoke method '"+methodName+"' on it");
      }
    }

    if (methodName!=null) {
      // method invocation on object or static method invocation in case object is null
      if (object!=null) {
        clazz = object.getClass();
      }
      try {
        Object[] args = ObjectDescriptor.getArgs(wireContext, argDescriptors);
        Method method = ReflectUtil.findMethod(clazz, methodName, argDescriptors, args);
        if (method==null) {
          // throw exception but first, generate decent message
          throw new WireException("method "+ReflectUtil.getSignature(methodName, argDescriptors, args)+" is not available on "+
              (object!=null ? "object "+object+" ("+clazz.getName()+")" : "class "+clazz.getName()));
        }
        if (object == null && (!Modifier.isStatic(method.getModifiers()))) {
          // A non static method is invoked on a null object
          throw new WireException("method "+ clazz.getName() + "." + ReflectUtil.getSignature(methodName, argDescriptors, args)+" is not static. It cannot be called on a null object.");
        }
        object = ReflectUtil.invoke(method, object, args);

      } catch (WireException e) {
        throw e;
      } catch (Exception e) {
        throw new WireException("couldn't invoke factory method "+methodName+": "+e.getMessage(), e);
      }
    }

    return object;
  }

  /**
   * Initializes the specified object.
   * If auto-wiring was set to <code>true</code>, auto-wiring is performed (see {@link #autoWire(Object, WireContext)}). Fields and properties injections are then performed.
   *
   */
  public void initialize(Object object, WireContext wireContext) {
    try {
      // specified operations takes precedence over auto-wiring.
      // e.g. in case there is a collision between
      // a field or property injection and an autowired value,
      // the field or property injections should win.
      // That is why autowiring is done first
      if (isAutoWireEnabled) {
        autoWire(object, wireContext);
      }
      if (operations!=null) {
        for(Operation operation: operations) {
          operation.apply(object, wireContext);
        }
      }
    } catch (Exception e) {
      throw new WireException("couldn't initialize object '"+(name!=null ? name : className)+"': "+e.getMessage(), e);
    }
  }

  public Class<?> getType(WireDefinition wireDefinition) {
    if (className!=null) {
      try {
        return ReflectUtil.loadClass(wireDefinition.getClassLoader(), className);
      } catch (JbpmReflectException e) {
        throw new WireException("couldn't create '"+(name!=null ? name : className)+"': "+e.getMessage(), e.getCause());
      }
    }
   
    Descriptor descriptor = null;
    if (factoryDescriptor!=null) {
      descriptor = factoryDescriptor;
    } else if (factoryObjectName!=null) {
      descriptor = wireDefinition.getDescriptor(factoryObjectName);
    }

    if (descriptor!=null) {
      Class<?> factoryClass = descriptor.getType(wireDefinition);
      if (factoryClass!=null) {
        Method method = ReflectUtil.findMethod(factoryClass, methodName, argDescriptors, null);
        if (method!=null) {
          return method.getReturnType();
        }
      }
    }

    return null;
  }
 
  /**
   * Auto wire object present in the context and the specified object's fields.
   * @param object object on which auto-wiring is performed.
   * @param wireContext context in which the wiring objects are searched.
   */
  protected void autoWire(Object object, WireContext wireContext) {
    Class<?> clazz = object.getClass();
    while (clazz!=null) {
      Field[] declaredFields = clazz.getDeclaredFields();
      if (declaredFields!=null) {
        for (Field field: declaredFields) {
          if (! Modifier.isStatic(field.getModifiers())) {
            String fieldName = field.getName();

            Object autoWireValue = null;
            if ("environment".equals(fieldName)) {
              autoWireValue = wireContext.getEnvironment();

            } else if ( ("context".equals(fieldName))
                || ("wireContext".equals(fieldName))
            ) {
              autoWireValue = wireContext;

            } else if ("environmentFactory".equals(fieldName)) {
              autoWireValue = wireContext.getEnvironment().getEnvironmentFactory();

            } else if (wireContext.has(fieldName)) {
              autoWireValue = wireContext.get(fieldName);

            } else {
              autoWireValue = wireContext.get(field.getType());
            }

            if (autoWireValue!=null) {
              try {
                log.fine("auto wiring field "+fieldName+" in "+name);
                ReflectUtil.set(field, object, autoWireValue);
              } catch (PvmException e) {
                if(e.getCause() instanceof IllegalArgumentException) {
                  log.warning("couldn't auto wire "+fieldName+" (of type "+field.getType().getName()+") " +
                      "with value "+autoWireValue + " (of type "+autoWireValue.getClass().getName()+")");
                } else {
                  log.warning("couldn't auto wire "+fieldName+" with value "+autoWireValue);
                }
              }
            }
          }
        }
      }
      clazz = clazz.getSuperclass();
    }
  }

  /**
   * Creates a list of arguments (objects) from a list of argument descriptors.
   * @param wireContext context used to create objects.
   * @param argDescriptors list of argument descriptors.
   * @return a list of object created from the descriptors.
   * @throws Exception
   */
  public static Object[] getArgs(WireContext wireContext, List<ArgDescriptor> argDescriptors) throws Exception {
    Object[] args = null;
    if (argDescriptors!=null) {
      args = new Object[argDescriptors.size()];
      for(int i=0; i<argDescriptors.size(); i++) {
        ArgDescriptor argDescriptor = argDescriptors.get(i);
        Object arg;
        try {
          arg = wireContext.create(argDescriptor.getDescriptor(), true);
          args[i] = arg;
        } catch (RuntimeException e) {
          // i have made sure that the runtime exception is caught everywhere the getArgs method
          // is used so that a better descriptive exception can be rethrown
          throw new Exception("couldn't create argument "+i+": "+e.getMessage(), e);
        }
      }
    }
    return args;
  }

  /**
   * Adds a argument descriptor to the list of arguments descriptor to used when invoking the specified method.
   * @param argDescriptor argument descriptor to add.
   */
  public void addArgDescriptor(ArgDescriptor argDescriptor) {
    if (argDescriptors==null) {
      argDescriptors = new ArrayList<ArgDescriptor>();
    }
    argDescriptors.add(argDescriptor);
  }

  /**
   * Adds an operation to perform during initialization.
   * @param operation operation to add.
   */
  public void addOperation(Operation operation) {
    if (operations==null) {
      operations = new ArrayList<Operation>();
    }
    operations.add(operation);
  }
 
  /** convenience method to add a type based field injection */
  public void addTypedInjection(Class<?> type, String fieldName) {
    TypeRefDescriptor typeRefDescriptor = new TypeRefDescriptor();
    typeRefDescriptor.setEnvironmentClass(type);
    FieldOperation injectionOperation = new FieldOperation();
    injectionOperation.setFieldName(fieldName);
    injectionOperation.setDescriptor(typeRefDescriptor);
    addOperation(injectionOperation);
  }

  /**
   * Gets the class name of the object to create.
   * This name is defined only when creating objects from a constructor or when invoking static methods.
   * @return the name of the class of the object to create.
   */
  public String getClassName() {
    return className;
  }

  /**
   * Sets class name of the object to create.
   * This name is defined only when creating objects from a constructor or when invoking static methods.
   * If this name is set, the factoryObjectName and factoryDescriptor should not be set.
   * @see #setFactoryDescriptor(Descriptor)
   * @see #setFactoryObjectName(String)
   * @param className name of the class to use.
   */
  public void setClassName(String className) {
    this.className = className;
  }

  /**
   * Gets the list of descriptors to use to create method arguments.
   * @return list of descriptors to use to create method arguments.
   */
  public List<ArgDescriptor> getArgDescriptors() {
    return argDescriptors;
  }

  /**
   * Sets the list of descriptors to use to create method arguments.
   * @param argDescriptors list of descriptors to use to create method arguments.
   */
  public void setArgDescriptors(List<ArgDescriptor> argDescriptors) {
    this.argDescriptors = argDescriptors;
  }

  /**
   * Gets the list of operations to perform during initialization.
   * @return list of operations to perform during initialization.
   */
  public List<Operation> getOperations() {
    return operations;
  }

  /**
   * Sets the list of operations to perform during initialization.
   * @param operations list of operations to perform during initialization.
   */
  public void setOperations(List<Operation> operations) {
    this.operations = operations;
  }

  /**
   * Gets the Descriptor from which the object should be created.
   * @return the Descriptor from which the object should be created.
   */
  public Descriptor getFactoryDescriptor() {
    return factoryDescriptor;
  }

  /**
   * Sets the Descriptor from which the object should be created.
   * If this Descriptor is set, the className and factoryObjectName should not be set.
   * @see #setClassName(String)
   * @see #setFactoryObjectName(String)
   * @param factoryDescriptor the Descriptor from which the object should be created.
   */
  public void setFactoryDescriptor(Descriptor factoryDescriptor) {
    this.factoryDescriptor = factoryDescriptor;
  }


  /**
   * Gets the name of the object to get from the WireContext.
   * @return name of the object to get from the WireContext.
   */
  public String getFactoryObjectName() {
    return factoryObjectName;
  }

  /**
   * Sets name of the object to get from the WireContext.
   * If this name is set, the className and factoryDescriptor should not be set.
   * @see #setClassName(String)
   * @see #setFactoryDescriptor(Descriptor)
   * @param factoryObjectName name of the object to get from the WireContext.
   */
  public void setFactoryObjectName(String factoryObjectName) {
    this.factoryObjectName = factoryObjectName;
  }

  /**
   * Gets the name of the method to invoke.
   * @return name of the method to invoke.
   */
  public String getMethodName() {
    return methodName;
  }

  /**
   * Sets the name of the method to invoke.
   * @param methodName name of the method to invoke.
   */
  public void setMethodName(String methodName) {
    this.methodName = methodName;
  }

  /**
   * Checks if auto-wiring is enabled
   * @return <code>true</code> if auto-wiring is enabled.
   */
  public boolean isAutoWireEnabled() {
    return isAutoWireEnabled;
  }

  /**
   * Enables/Disables auto wiring mode.
   * @param isAutoWireEnabled <code>true</code> to enable auto-wiring.
   */
  public void setAutoWireEnabled(boolean isAutoWireEnabled) {
    this.isAutoWireEnabled = isAutoWireEnabled;
  }
}
TOP

Related Classes of org.jbpm.wire.descriptor.ObjectDescriptor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.