ludc
2025-01-16 986aa62ed00bee39363bab41b4eeb8259d446efd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
package org.jbpm.pvm.internal.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.Arrays;
import java.util.List;
 
import org.jbpm.api.JbpmException;
import org.jbpm.internal.log.Log;
import org.jbpm.pvm.internal.env.EnvironmentImpl;
import org.jbpm.pvm.internal.script.ScriptManager;
import org.jbpm.pvm.internal.util.ReflectUtil;
import org.jbpm.pvm.internal.wire.Descriptor;
import org.jbpm.pvm.internal.wire.JbpmClassNotFoundException;
import org.jbpm.pvm.internal.wire.WireContext;
import org.jbpm.pvm.internal.wire.WireDefinition;
import org.jbpm.pvm.internal.wire.WireException;
import org.jbpm.pvm.internal.wire.operation.FieldOperation;
import org.jbpm.pvm.internal.wire.operation.Operation;
import org.jbpm.pvm.internal.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='#createTime'>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='createTime'>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 Log log = Log.getLog(ObjectDescriptor.class.getName());
 
  protected 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;
 
  protected String expr;
  
  protected String lang;
  
  /** specifies the object on which to invoke the method.
   * Either className, objectName or a descriptor has to be specified. */
  protected Descriptor factoryDescriptor = null;
 
  protected String methodName = null;
 
  /** map to db as a component */
  protected List<ArgDescriptor> argDescriptors = null;
  /** list of operations to perform during initialization. */
  protected List<Operation> operations = null;
 
  /** True if autowiring is enabled.  */
  protected boolean isAutoWireEnabled = false;
  
  public ObjectDescriptor() {
  }
 
  public ObjectDescriptor(String className) {
    this.className = className;
  }
 
  public ObjectDescriptor(Class<?> clazz) {
    this.className = clazz.getName();
  }
 
  /**
   * 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 = Thread.currentThread().getContextClassLoader();
        clazz = Class.forName(className, true, classLoader);
      } catch (Exception e) {
        throw new JbpmClassNotFoundException("couldn't load class "+className, 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 "+Arrays.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");
      }
    } else if (expr!=null) {
      ScriptManager scriptManager = ScriptManager.getScriptManager();
      object = scriptManager.evaluateExpression(expr, lang);
    }
 
    if (methodName!=null) {
      try {
        object = invokeMethod(methodName, argDescriptors, wireContext, object, clazz);
 
      } catch (WireException e) {
        throw e;
      } catch (Exception e) {
        throw new WireException("couldn't invoke factory method "+methodName+": "+e.getMessage(), e);
      }
    }
 
    return object;
  }
 
  public static Object invokeMethod(String methodName, List<ArgDescriptor> argDescriptors, WireContext wireContext, Object object, Class< ? > clazz) throws Exception {
    // method invocation on object or static method invocation in case object is null
    if (object!=null) {
      clazz = object.getClass();
    }
    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);
    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 {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        return Class.forName(className, true, classLoader);
      } catch (Exception e) {
        throw new WireException("couldn't load class '"+className+"'", e);
      }
    }
    
    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();
            Class<?> fieldType = field.getType();
 
            Object autoWireValue = null;
            if ("environment".equals(fieldName)) {
              autoWireValue = EnvironmentImpl.getCurrent();
 
            } else if ( ("context".equals(fieldName))
                || ("wireContext".equals(fieldName))
            ) {
              autoWireValue = wireContext;
 
            } else if (wireContext.has(fieldName)) {
              autoWireValue = wireContext.get(fieldName);
 
            } else {
              autoWireValue = wireContext.get(fieldType);  
            }
            // if auto wire value has not been found in current context, 
            // search in environment
            if (autoWireValue == null) {
              EnvironmentImpl currentEnvironment = EnvironmentImpl.getCurrent();
              if (currentEnvironment != null) {
                autoWireValue = currentEnvironment.get(fieldName); 
                if (autoWireValue == null) {
                  autoWireValue = currentEnvironment.get(fieldType);
                }
              }
            }
            
            if (autoWireValue!=null) {
              try {
                if (log.isTraceEnabled()) log.trace("auto wiring field "+fieldName+" in "+name);
                ReflectUtil.set(field, object, autoWireValue);
              } catch (JbpmException e) {
                if(e.getCause() instanceof IllegalArgumentException) {
                  log.info("WARNING: couldn't auto wire "+fieldName+" (of type "+fieldType.getName()+") " +
                      "with value "+autoWireValue + " (of type "+autoWireValue.getClass().getName()+")");
                } else {
                  log.info("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(String fieldName, Class<?> type) {
    addInjection(fieldName, new EnvDescriptor(type));
  }
 
  /** add a field injection based on a descriptor */
  public void addInjection(String fieldName, Descriptor descriptor) {
    FieldOperation injectionOperation = new FieldOperation();
    injectionOperation.setFieldName(fieldName);
    injectionOperation.setDescriptor(descriptor);
    addOperation(injectionOperation);
  }
 
  /** add a property injection based on a descriptor */
  public void addPropertyInjection(String propertyName,
      Descriptor valueDescriptor) {
    PropertyOperation operation = new PropertyOperation();
    operation.setPropertyName(propertyName);
    operation.setDescriptor(valueDescriptor);
    addOperation(operation);
  }
 
 
  // getters and setters //////////////////////////////////////////////////////
  
  public String getClassName() {
    return className;
  }
  public void setClassName(String className) {
    this.className = className;
  }
  public List<ArgDescriptor> getArgDescriptors() {
    return argDescriptors;
  }
  public void setArgDescriptors(List<ArgDescriptor> argDescriptors) {
    this.argDescriptors = argDescriptors;
  }
  public List<Operation> getOperations() {
    return operations;
  }
  public void setOperations(List<Operation> operations) {
    this.operations = operations;
  }
  public Descriptor getFactoryDescriptor() {
    return factoryDescriptor;
  }
  public void setFactoryDescriptor(Descriptor factoryDescriptor) {
    this.factoryDescriptor = factoryDescriptor;
  }
  public String getFactoryObjectName() {
    return factoryObjectName;
  }
  public void setFactoryObjectName(String factoryObjectName) {
    this.factoryObjectName = factoryObjectName;
  }
  public String getMethodName() {
    return methodName;
  }
  public void setMethodName(String methodName) {
    this.methodName = methodName;
  }
  public boolean isAutoWireEnabled() {
    return isAutoWireEnabled;
  }
  public void setAutoWireEnabled(boolean isAutoWireEnabled) {
    this.isAutoWireEnabled = isAutoWireEnabled;
  }
  public String getExpr() {
    return expr;
  }
  public void setExpr(String expr) {
    this.expr = expr;
  }
  public String getLang() {
    return lang;
  }
  public void setLang(String lang) {
    this.lang = lang;
  }
}