View Javadoc

1   /*
2    Copyright (C) 2007 Grid Systems, S.A. and University of Southampton IT Innovation Centre
3   
4    This library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Lesser General Public
6    License as published by the Free Software Foundation; either
7    version 2.1 of the License, or (at your option) any later version.
8   
9    This library is distributed in the hope that it will be useful,
10   but WITHOUT ANY WARRANTY; without even the implied warranty of
11   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12   Lesser General Public License for more details.
13  
14   You should have received a copy of the GNU Lesser General Public
15   License along with this library; if not, write to the Free Software
16   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
17  */
18  package uk.ac.soton.itinnovation.nextgrid.api.pom;
19  
20  import java.lang.reflect.InvocationTargetException;
21  import java.lang.reflect.Method;
22  import java.lang.reflect.Modifier;
23  import java.net.URI;
24  import java.util.Collections;
25  import java.util.HashMap;
26  import java.util.Iterator;
27  import java.util.Map;
28  import java.util.WeakHashMap;
29  
30  import javax.xml.namespace.QName;
31  
32  import nextgrid.api.env.ProcessEnvironment;
33  import nextgrid.api.pom.ProcessException;
34  import nextgrid.api.pom.Reference;
35  import nextgrid.api.pom.Service;
36  
37  import org.apache.commons.logging.Log;
38  import org.apache.commons.logging.LogFactory;
39  
40  import uk.ac.soton.itinnovation.nextgrid.api.pom.exceptions.ExecutionException;
41  
42  import com.gridsystems.nextgrid.api.pom.AbstractGroundingImpl;
43  
44  /**
45   * Grounding implementation that executes a Java method.
46   *
47   * @author Nikolaos Matskanis
48   * @author Rodrigo Ruiz
49   */
50  public class JavaGroundingImpl extends AbstractGroundingImpl {
51  
52    /**
53     * <code>serialVersionUID</code> attribute.
54     */
55    private static final long serialVersionUID = -7991464254187036277L;
56  
57    /**
58     * Class logger.
59     */
60    private static final Log LOG = LogFactory.getLog(JavaGroundingImpl.class);
61  
62    /**
63     * Qualified Java class name.
64     */
65    public static final QName ATTR_JAVA_CLASS = attr("java-class");
66  
67    /**
68     * Java class name of the instance factory to be used.
69     */
70    public static final QName ATTR_JAVA_FACTORY = attr("java-factory");
71  
72    /**
73     * Name passed to the factory to retrieve the instance. If not specified,
74     * the service ID will be used as a default value.
75     */
76    public static final QName ATTR_JAVA_NAME = attr("java-factory-name");
77  
78    /**
79     * Method name.
80     */
81    public static final QName ATTR_JAVA_METHOD = attr("java-method");
82  
83    /**
84     * Comma-separated list of input parameter names.
85     */
86    public static final QName ATTR_PARAM_ORDER = attr("parameter-order");
87  
88    /**
89     * Map of supported attribute names and descriptions.
90     */
91    private static final Map<QName, String> ATTRIBS;
92  
93    /**
94     * Grounding type.
95     */
96    public static final URI TYPE = URI.create("urn:java");
97  
98    /**
99     * Instances cache.
100    */
101   private static final WeakHashMap<Service, Object> CACHE
102     = new WeakHashMap<Service, Object>();
103 
104   static {
105     Map<QName, String> attrs = new HashMap<QName, String>();
106     attrs.put(ATTR_JAVA_CLASS, "Qualified name of the Java class to invoke");
107     attrs.put(ATTR_JAVA_FACTORY, "Qualified name of Java class to use as factory");
108     attrs.put(ATTR_JAVA_NAME, "Identifier passed to the factory");
109     attrs.put(ATTR_JAVA_METHOD, "Method name to invoke");
110     attrs.put(ATTR_PARAM_ORDER, "Comma-separated list of input parameters");
111     ATTRIBS = Collections.unmodifiableMap(attrs);
112   }
113 
114   /**
115    * Creates a new instance.
116    */
117   public JavaGroundingImpl() {
118     super(TYPE, ATTRIBS);
119   }
120 
121   /**
122    * {@inheritDoc}
123    */
124   public void invoke(ProcessEnvironment env, Service svc) throws ProcessException {
125     if (svc == null) {
126       throw new ProcessException("No service associated to this grounding");
127     }
128     try {
129       LOG.trace("Grounding '" + svc.getName() + "' execution start");
130       String fClass = svc.getAttribute(ATTR_JAVA_CLASS);
131       String fMethod = svc.getAttribute(ATTR_JAVA_METHOD);
132       String fOrder = svc.getAttribute(ATTR_PARAM_ORDER);
133 
134       checkNull(fClass, ATTR_JAVA_CLASS.toString());
135       checkNull(fMethod, ATTR_JAVA_METHOD.toString());
136 
137       Class<?> claz = null;
138       Method method = null;
139 
140       String[] paramNames = (fOrder == null) ? new String[0] : fOrder.split(",");
141       Class<?>[] paramTypes = new Class[paramNames.length];
142       Object[] paramValues = new Object[paramTypes.length];
143 
144       for (int i = 0; i < paramNames.length; i++) {
145         Reference<?> ref = svc.getInput(paramNames[i]);
146         checkNull(ref, "Reference '" + paramNames[i] + "'");
147         paramTypes[i] = ref.getValueType();
148         paramValues[i] = ref.getValue();
149       }
150 
151       try {
152         claz = Class.forName(fClass);
153         method = claz.getDeclaredMethod(fMethod, paramTypes);
154       } catch (ClassNotFoundException e) {
155         throw new ExecutionException("Class '" + fClass + "' not found.", e);
156       } catch (NoSuchMethodException e) {
157         throw new ExecutionException("Method '" + fMethod + "' not found.", e);
158       } catch (Exception e) {
159         throw new ExecutionException(e);
160       }
161 
162 
163       //invoke java method
164       try {
165         Object obj = null;
166         if (!Modifier.isStatic(method.getModifiers())) {
167           obj = getInstance(svc, claz);
168         }
169         if (method.getReturnType().toString().equalsIgnoreCase("void")) {
170           method.invoke(obj, paramValues);
171         } else {
172           Object result = method.invoke(obj, paramValues);
173           writeResult(svc, result);
174         }
175       } catch (InvocationTargetException e) {
176         throw new ExecutionException("Error in executed method", e.getTargetException());
177       } catch (Exception e) {
178         throw new ExecutionException("Error in executed method", e);
179       }
180     } finally {
181       LOG.trace("Grounding '" + svc.getName() + "' execution end");
182     }
183   }
184 
185   /**
186    * Validation method for null values.
187    *
188    * @param obj  The object to check
189    * @param name The object name, for exception messages
190    * @throws ProcessException If the object is null
191    */
192   private void checkNull(Object obj, String name) throws ProcessException {
193     if (obj == null) {
194       throw new ProcessException(name + " not defined",
195         new NullPointerException(name));
196     }
197   }
198 
199   /**
200    * {@inheritDoc}
201    */
202   public void dispose() {
203   }
204 
205   /**
206    * {@inheritDoc}
207    */
208   public void validate() {
209   }
210 
211   /**
212    * Gets an instance of the target class.
213    * <p>
214    * This method tries to retrieve the instance through a simple factory
215    * pattern. If the target class contains a static method with signature:
216    * <pre>
217    * getInstance(URI)
218    * </pre>
219    *
220    * It will be used to get the instance. This method would allow the target
221    * class to cache instances for specific services, or implement a singleton
222    * pattern.
223    *
224    * @param service The service for which the instance is being created
225    * @param c The target class
226    * @return An instance
227    */
228   @SuppressWarnings("unchecked")
229   private Object getInstance(Service service, Class c) {
230     assert service != null;
231 
232     Object instance = CACHE.get(service);
233     if (instance != null) {
234       return instance;
235     } else {
236       final Object obj;
237 
238       try {
239         String factName = service.getAttribute(ATTR_JAVA_FACTORY);
240         if (factName == null) {
241           obj = c.newInstance();
242         } else {
243           Class factory = Class.forName(factName);
244           Method mFactory = factory.getDeclaredMethod("getInstance", URI.class);
245           obj = mFactory.invoke(null, service.getId());
246         }
247       } catch (Exception e) {
248         throw new ExecutionException("Error getting java instance", e);
249       }
250 
251       CACHE.put(service, obj);
252       return obj;
253     }
254   }
255 
256   /**
257    * Writes the result value into the first output parameter of the service.
258    *
259    * @param service The service to write to
260    * @param value The value to set
261    */
262   @SuppressWarnings("unchecked")
263   private void writeResult(Service service, Object value) {
264     Iterator<Reference<?>> it = service.getOutputs().values().iterator();
265     if (it.hasNext()) {
266       Reference r = it.next();
267       r.setValue(value);
268     } else {
269       LOG.info("No output parameters found. Return value ignored");
270     }
271   }
272 }