View Javadoc

1   /*
2    * Copyright 2002,2004 The Apache Software Foundation.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.apache.commons.jelly.tags.ant;
18  
19  import java.lang.reflect.Constructor;
20  import java.lang.reflect.InvocationTargetException;
21  import java.lang.reflect.Method;
22  import java.util.Iterator;
23  import java.util.Map;
24  
25  import org.apache.commons.beanutils.BeanUtils;
26  import org.apache.commons.beanutils.MethodUtils;
27  import org.apache.commons.jelly.JellyTagException;
28  import org.apache.commons.jelly.MapTagSupport;
29  import org.apache.commons.jelly.Tag;
30  import org.apache.commons.jelly.XMLOutput;
31  import org.apache.commons.jelly.expression.Expression;
32  import org.apache.commons.jelly.impl.BeanSource;
33  import org.apache.commons.jelly.impl.StaticTag;
34  import org.apache.commons.logging.Log;
35  import org.apache.commons.logging.LogFactory;
36  import org.apache.tools.ant.BuildException;
37  import org.apache.tools.ant.IntrospectionHelper;
38  import org.apache.tools.ant.Project;
39  import org.apache.tools.ant.Task;
40  import org.apache.tools.ant.TaskAdapter;
41  import org.apache.tools.ant.TaskContainer;
42  import org.apache.tools.ant.types.DataType;
43  
44  /***
45   * Tag supporting ant's Tasks as well as
46   * dynamic runtime behaviour for 'unknown' tags.
47   *
48   * @author <a href="mailto:bob@eng.werken.com">bob mcwhirter</a>
49   * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
50   */
51  public class AntTag extends MapTagSupport implements TaskSource {
52  
53      /*** The Log to which logging calls will be made. */
54      private static final Log log = LogFactory.getLog(AntTag.class);
55  
56      private static final Class[] addTaskParamTypes = { String.class };
57  
58      /*** store the name of the manifest tag for special handling */
59      private static final String ANT_MANIFEST_TAG = "manifest";
60  
61      /*** The name of this tag. */
62      protected String tagName;
63  
64      /*** The general object underlying this tag. */
65      protected Object object;
66  
67      /*** Task, if this tag represents a task. */
68      protected Task task;
69  
70  
71      /*** Construct with a project and tag name.
72       *
73       *  @param tagName The name on the tag.
74       */
75      public AntTag(String tagName) {
76          this.tagName = tagName;
77      }
78  
79      public String toString() {
80          return "[AntTag: name=" + getTagName() + "]";
81      }
82  
83      // TaskSource interface
84      //-------------------------------------------------------------------------
85  
86      /*** Retrieve the general object underlying this tag.
87       *
88       *  @return The object underlying this tag.
89       */
90      public Object getTaskObject() {
91          return this.object;
92      }
93  
94      /***
95       * Allows nested tags to set a property on the task object of this tag
96       */
97      public void setTaskProperty(String name, Object value) throws JellyTagException {
98          Object object = getTaskObject();
99          if ( object != null ) {
100             setBeanProperty( object, name, value );
101         }
102     }
103 
104     // Tag interface
105     //-------------------------------------------------------------------------
106     public void doTag(XMLOutput output) throws JellyTagException {
107 
108         Project project = getAntProject();
109         String tagName = getTagName();
110         Object parentObject = findBeanAncestor();
111         Object parentTask = findParentTaskObject();
112 
113         // lets assume that Task instances are not nested inside other Task instances
114         // for example <manifest> inside a <jar> should be a nested object, where as
115         // if the parent is not a Task the <manifest> should create a ManifestTask
116         //
117         // also its possible to have a root Ant tag which isn't a task, such as when
118         // defining <fileset id="...">...</fileset>
119 
120         Object nested = null;
121         if (parentObject != null && !( parentTask instanceof TaskContainer) ) {
122             nested = createNestedObject( parentObject, tagName );
123         }
124 
125         if (nested == null) {
126             task = createTask( tagName );
127 
128             if (task != null) {
129 
130                 if ( log.isDebugEnabled() ) {
131                     log.debug( "Creating an ant Task for name: " + tagName );
132                 }
133 
134                 // the following algorithm follows the lifetime of a tag
135                 // http://jakarta.apache.org/ant/manual/develop.html#writingowntask
136                 // kindly recommended by Stefan Bodewig
137 
138                 // create and set its project reference
139                 if ( task instanceof TaskAdapter ) {
140                     setObject( ((TaskAdapter)task).getProxy() );
141                 }
142                 else {
143                     setObject( task );
144                 }
145 
146                 // set the task ID if one is given
147                 Object id = getAttributes().remove( "id" );
148                 if ( id != null ) {
149                     project.addReference( (String) id, task );
150                 }
151 
152                 // ### we might want to spoof a Target setting here
153 
154                 // now lets initialize
155                 task.init();
156 
157                 // now lets invoke the body to call all the createXXX() or addXXX() methods
158                 String body = getBodyText();
159 
160                 // now lets set any attributes of this tag...
161                 setBeanProperties();
162 
163                 // now lets set the addText() of the body content, if its applicaable
164                 Method method = MethodUtils.getAccessibleMethod( task.getClass(),
165                                                                  "addText",
166                                                                  addTaskParamTypes );
167                 if (method != null) {
168                     Object[] args = { body };
169                     try {
170                         method.invoke(this.task, args);
171                     }
172                     catch (IllegalAccessException e) {
173                         throw new JellyTagException(e);
174                     }
175                     catch (InvocationTargetException e) {
176                         throw new JellyTagException(e);
177                     }
178                 }
179 
180                 // now lets set all the attributes of the child elements
181                 // XXXX: to do!
182 
183                 // now we're ready to invoke the task
184                 // XXX: should we call execute() or perform()?
185                 task.perform();
186             }
187         }
188 
189         if (task == null) {
190 
191             if (nested == null) {
192 
193                 if ( log.isDebugEnabled() ) {
194                     log.debug( "Trying to create a data type for tag: " + tagName );
195                 }
196                 nested = createDataType( tagName );
197             }
198             else {
199                 if ( log.isDebugEnabled() ) {
200                     log.debug( "Created nested property tag: " + tagName );
201                 }
202             }
203 
204             if ( nested != null ) {
205                 setObject( nested );
206 
207                 // set the task ID if one is given
208                 Object id = getAttributes().remove( "id" );
209                 if ( id != null ) {
210                     project.addReference( (String) id, nested );
211                 }
212 
213                 // TODO: work out why we always set the name attribute.
214                 // See JELLY-105.
215 //                try{
216 //                    PropertyUtils.setProperty( nested, "name", tagName );
217 //                }
218 //                catch (Exception e) {
219 //                    log.warn( "Caught exception setting nested name: " + tagName, e );
220 //                }
221 
222                 // now lets invoke the body
223                 String body = getBodyText();
224 
225                 // now lets set any attributes of this tag...
226                 setBeanProperties();
227 
228                 // now lets add it to its parent
229                 if ( parentObject != null ) {
230                     IntrospectionHelper ih = IntrospectionHelper.getHelper( parentObject.getClass() );
231                     try {
232                         if (log.isDebugEnabled()) {
233                             log.debug("About to set the: " + tagName
234                                 + " property on: " + parentObject + " to value: "
235                                 + nested + " with type: " + nested.getClass()
236                             );
237                         }
238 
239                         ih.storeElement( project, parentObject, nested, tagName.toLowerCase() );
240                     }
241                     catch (Exception e) {
242                         log.warn( "Caught exception setting nested: " + tagName, e );
243                     }
244 
245                     // now try to set the property for good measure
246                     // as the storeElement() method does not
247                     // seem to call any setter methods of non-String types
248                     try {
249                         BeanUtils.setProperty( parentObject, tagName, nested );
250                     }
251                     catch (Exception e) {
252                         log.debug("Caught exception trying to set property: " + tagName + " on: " + parentObject);
253                     }
254                 }
255             }
256             else {
257                 log.warn("Could not convert tag: " + tagName + " into an Ant task, data type or property");
258 
259                 // lets treat this tag as static XML...
260                 StaticTag tag = new StaticTag("", tagName, tagName);
261                 tag.setParent( getParent() );
262                 tag.setBody( getBody() );
263 
264                 tag.setContext(context);
265 
266                 for (Iterator iter = getAttributes().entrySet().iterator(); iter.hasNext();) {
267                     Map.Entry entry = (Map.Entry) iter.next();
268                     String name = (String) entry.getKey();
269                     Object value = entry.getValue();
270 
271                     tag.setAttribute(name, value);
272                 }
273 
274                 tag.doTag(output);
275             }
276         }
277     }
278 
279 
280     // Properties
281     //-------------------------------------------------------------------------
282     public String getTagName() {
283         return this.tagName;
284     }
285 
286     /*** Set the object underlying this tag.
287      *
288      *  @param object The object.
289      */
290     public void setObject(Object object) {
291         this.object = object;
292     }
293 
294     public Project getAntProject() {
295         Project project = AntTagLibrary.getProject(context);
296         if (project == null) {
297             throw new NullPointerException("No Ant Project object is available");
298         }
299         return project;
300     }
301 
302     // Implementation methods
303     //-------------------------------------------------------------------------
304 
305     /***
306      * Sets the properties on the Ant task
307      */
308     public void setBeanProperties() throws JellyTagException {
309         Object object = getTaskObject();
310         if ( object != null ) {
311             Map map = getAttributes();
312             for ( Iterator iter = map.entrySet().iterator(); iter.hasNext(); ) {
313                 Map.Entry entry = (Map.Entry) iter.next();
314                 String name = (String) entry.getKey();
315                 Object value = entry.getValue();
316                 setBeanProperty( object, name, value );
317             }
318         }
319     }
320 
321     public void setAttribute(String name, Object value) {
322         if ( value == null ) {
323             // should we send in null?
324             super.setAttribute( name, "" );
325         }
326         else {
327             if ( value instanceof Expression )
328             {
329                 super.setAttribute( name, ((Expression) value).evaluateRecurse(context) );
330             }
331             else
332             {
333                 super.setAttribute( name, value.toString() );
334             }
335         }
336     }
337 
338     public void setBeanProperty(Object object, String name, Object value) throws JellyTagException {
339         if ( log.isDebugEnabled() ) {
340             log.debug( "Setting bean property on: "+  object + " name: " + name + " value: " + value );
341         }
342 
343         IntrospectionHelper ih = IntrospectionHelper.getHelper( object.getClass() );
344 
345         if ( value instanceof String ) {
346             try {
347                 ih.setAttribute( getAntProject(), object, name.toLowerCase(), (String) value );
348                 return;
349             }
350             catch (Exception e) {
351                 // ignore: not a valid property
352             }
353         }
354 
355         try {
356 
357             ih.storeElement( getAntProject(), object, value, name );
358         }
359         catch (Exception e) {
360 
361             try {
362                 // let any exceptions bubble up from here
363                 BeanUtils.setProperty( object, name, value );
364             }
365             catch (IllegalAccessException ex) {
366                 throw new JellyTagException(ex);
367             }
368             catch (InvocationTargetException ex) {
369                 throw new JellyTagException(ex);
370             }
371         }
372     }
373 
374 
375     /***
376      * Creates a nested object of the given object with the specified name
377      */
378     public Object createNestedObject(Object object, String name) {
379         Object dataType = null;
380         if ( object != null ) {
381             IntrospectionHelper ih = IntrospectionHelper.getHelper( object.getClass() );
382 
383             if ( ih != null ) {
384                 try {
385                     dataType = ih.createElement( getAntProject(), object, name.toLowerCase() );
386                 } catch (BuildException be) {
387                     if (object instanceof Tag)
388                     {
389                         if (log.isDebugEnabled()) {
390                             log.debug("Failed attempt to create an ant datatype for a jelly tag", be);
391                         }
392                     } else {
393                         log.error(be);
394                     }
395                 }
396             }
397         }
398 
399         if ( dataType == null ) {
400             dataType = createDataType( name );
401         }
402 
403         return dataType;
404     }
405 
406     public Object createDataType(String name) {
407 
408         Object dataType = null;
409 
410         Class type = (Class) getAntProject().getDataTypeDefinitions().get(name);
411 
412         if ( type != null ) {
413 
414             Constructor ctor = null;
415             boolean noArg = false;
416 
417             // DataType can have a "no arg" constructor or take a single
418             // Project argument.
419             try {
420                 ctor = type.getConstructor(new Class[0]);
421                 noArg = true;
422             }
423             catch (NoSuchMethodException nse) {
424                 try {
425                     ctor = type.getConstructor(new Class[] { Project.class });
426                     noArg = false;
427                 } catch (NoSuchMethodException nsme) {
428                     log.info("datatype '" + name
429                         + "' didn't have a constructor with an Ant Project", nsme);
430                 }
431             }
432 
433             if (noArg) {
434                 dataType = createDataType(ctor, new Object[0], name, "no-arg constructor");
435             }
436             else {
437                 dataType = createDataType(ctor, new Object[] { getAntProject() }, name, "an Ant project");
438             }
439             if (dataType != null) {
440                 ((DataType)dataType).setProject( getAntProject() );
441             }
442         }
443 
444         return dataType;
445     }
446 
447     /***
448      * @return an object create with the given constructor and args.
449      * @param ctor a constructor to use creating the object
450      * @param args the arguments to pass to the constructor
451      * @param name the name of the data type being created
452      * @param argDescription a human readable description of the args passed
453      */
454     private Object createDataType(Constructor ctor, Object[] args, String name, String argDescription) {
455         try {
456             Object datatype = ctor.newInstance(args);
457             return datatype;
458         } catch (InstantiationException ie) {
459             log.error("datatype '" + name + "' couldn't be created with " + argDescription, ie);
460         } catch (IllegalAccessException iae) {
461             log.error("datatype '" + name + "' couldn't be created with " + argDescription, iae);
462         } catch (InvocationTargetException ite) {
463             log.error("datatype '" + name + "' couldn't be created with " + argDescription, ite);
464         }
465         return null;
466     }
467 
468     /***
469      * @param taskName
470      * @return
471      * @throws JellyTagException
472      */
473     public Task createTask(String taskName) throws JellyTagException {
474         return createTask( taskName,
475                            (Class) getAntProject().getTaskDefinitions().get( taskName ) );
476     }
477 
478     public Task createTask(String taskName,
479                            Class taskType) throws JellyTagException {
480 
481         if (taskType == null) {
482             return null;
483         }
484 
485         Object o = null;
486         try {
487             o = taskType.newInstance();
488         } catch (InstantiationException e) {
489             throw new JellyTagException(e);
490         }
491         catch (IllegalAccessException e) {
492             throw new JellyTagException(e);
493         }
494 
495         Task task = null;
496         if ( o instanceof Task ) {
497             task = (Task) o;
498         }
499         else {
500             TaskAdapter taskA=new TaskAdapter();
501             taskA.setProxy( o );
502             task=taskA;
503         }
504 
505         task.setProject(getAntProject());
506         task.setTaskName(taskName);
507 
508         return task;
509     }
510 
511     /***
512      * Attempts to look up in the parent hierarchy for a tag that implements the
513      * TaskSource interface, which returns an Ant Task object or that implements
514      * BeanSource interface which creates a bean,
515      * or will return the parent tag, which is also a bean.
516      */
517     protected Object findBeanAncestor() throws JellyTagException {
518         Tag tag = getParent();
519         while (tag != null) {
520             if (tag instanceof BeanSource) {
521                 BeanSource beanSource = (BeanSource) tag;
522                 return beanSource.getBean();
523             }
524             if (tag instanceof TaskSource) {
525                 TaskSource taskSource = (TaskSource) tag;
526                 return taskSource.getTaskObject();
527             }
528             tag = tag.getParent();
529         }
530         return getParent();
531     }
532 
533     /***
534      * Walks the hierarchy until it finds a parent TaskSource and returns its source or returns null
535      */
536     protected Object findParentTaskObject() throws JellyTagException {
537         Tag tag = getParent();
538         while (tag != null) {
539             if (tag instanceof TaskSource) {
540                 TaskSource source = (TaskSource) tag;
541                 return source.getTaskObject();
542             }
543             tag = tag.getParent();
544         }
545         return null;
546     }
547 
548 }