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