| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.commons.jelly.tags.ant; |
| |
| import java.io.PrintStream; |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.util.Iterator; |
| import java.util.Map; |
| |
| import org.apache.commons.beanutils.BeanUtils; |
| import org.apache.commons.beanutils.MethodUtils; |
| import org.apache.commons.jelly.JellyTagException; |
| import org.apache.commons.jelly.MapTagSupport; |
| import org.apache.commons.jelly.Tag; |
| import org.apache.commons.jelly.XMLOutput; |
| import org.apache.commons.jelly.expression.Expression; |
| import org.apache.commons.jelly.impl.BeanSource; |
| import org.apache.commons.jelly.impl.StaticTag; |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| import org.apache.tools.ant.BuildException; |
| import org.apache.tools.ant.DemuxOutputStream; |
| import org.apache.tools.ant.IntrospectionHelper; |
| import org.apache.tools.ant.Project; |
| import org.apache.tools.ant.Task; |
| import org.apache.tools.ant.TaskAdapter; |
| import org.apache.tools.ant.TaskContainer; |
| import org.apache.tools.ant.types.DataType; |
| |
| /** |
| * Tag supporting ant's Tasks as well as |
| * dynamic runtime behaviour for 'unknown' tags. |
| * |
| * @author <a href="mailto:bob@eng.werken.com">bob mcwhirter</a> |
| * @author <a href="mailto:jstrachan@apache.org">James Strachan</a> |
| */ |
| public class AntTag extends MapTagSupport implements TaskSource { |
| |
| /** The Log to which logging calls will be made. */ |
| private static final Log log = LogFactory.getLog(AntTag.class); |
| |
| private static final Class[] addTaskParamTypes = { String.class }; |
| |
| /** store the name of the manifest tag for special handling */ |
| private static final String ANT_MANIFEST_TAG = "manifest"; |
| |
| /** The name of this tag. */ |
| protected String tagName; |
| |
| /** The general object underlying this tag. */ |
| protected Object object; |
| |
| /** Task, if this tag represents a task. */ |
| protected Task task; |
| |
| |
| /** Construct with a project and tag name. |
| * |
| * @param tagName The name on the tag. |
| */ |
| public AntTag(String tagName) { |
| this.tagName = tagName; |
| } |
| |
| public String toString() { |
| return "[AntTag: name=" + getTagName() + "]"; |
| } |
| |
| // TaskSource interface |
| //------------------------------------------------------------------------- |
| |
| /** Retrieve the general object underlying this tag. |
| * |
| * @return The object underlying this tag. |
| */ |
| public Object getTaskObject() { |
| return this.object; |
| } |
| |
| /** |
| * Allows nested tags to set a property on the task object of this tag |
| */ |
| public void setTaskProperty(String name, Object value) throws JellyTagException { |
| Object object = getTaskObject(); |
| if ( object != null ) { |
| setBeanProperty( object, name, value ); |
| } |
| } |
| |
| // Tag interface |
| //------------------------------------------------------------------------- |
| public void doTag(XMLOutput output) throws JellyTagException { |
| |
| Project project = getAntProject(); |
| String tagName = getTagName(); |
| Object parentObject = findBeanAncestor(); |
| Object parentTask = findParentTaskObject(); |
| |
| // lets assume that Task instances are not nested inside other Task instances |
| // for example <manifest> inside a <jar> should be a nested object, where as |
| // if the parent is not a Task the <manifest> should create a ManifestTask |
| // |
| // also its possible to have a root Ant tag which isn't a task, such as when |
| // defining <fileset id="...">...</fileset> |
| |
| Object nested = null; |
| if (parentObject != null && !( parentTask instanceof TaskContainer) ) { |
| nested = createNestedObject( parentObject, tagName ); |
| } |
| |
| if (nested == null) { |
| task = createTask( tagName ); |
| |
| if (task != null) { |
| |
| if ( log.isDebugEnabled() ) { |
| log.debug( "Creating an ant Task for name: " + tagName ); |
| } |
| |
| // the following algorithm follows the lifetime of a tag |
| // http://jakarta.apache.org/ant/manual/develop.html#writingowntask |
| // kindly recommended by Stefan Bodewig |
| |
| // create and set its project reference |
| if ( task instanceof TaskAdapter ) { |
| setObject( ((TaskAdapter)task).getProxy() ); |
| } |
| else { |
| setObject( task ); |
| } |
| |
| // set the task ID if one is given |
| Object id = getAttributes().remove( "id" ); |
| if ( id != null ) { |
| project.addReference( (String) id, task ); |
| } |
| |
| // ### we might want to spoof a Target setting here |
| |
| // now lets initialize |
| task.init(); |
| |
| // now lets invoke the body to call all the createXXX() or addXXX() methods |
| String body = getBodyText(); |
| |
| // now lets set any attributes of this tag... |
| setBeanProperties(); |
| |
| // now lets set the addText() of the body content, if its applicable |
| Method method = MethodUtils.getAccessibleMethod( task.getClass(), |
| "addText", |
| addTaskParamTypes ); |
| if (method != null) { |
| Object[] args = { body }; |
| try { |
| method.invoke(this.task, args); |
| } |
| catch (IllegalAccessException e) { |
| throw new JellyTagException(e); |
| } |
| catch (InvocationTargetException e) { |
| throw new JellyTagException(e); |
| } |
| } |
| |
| // now lets set all the attributes of the child elements |
| // XXXX: to do! |
| |
| // now we're ready to invoke the task |
| // XXX: should we call execute() or perform()? |
| // according to org.apache.tools.ant.Main, redirect stdout and stderr |
| PrintStream initialOut = System.out; |
| PrintStream initialErr = System.err; |
| PrintStream newOut = new PrintStream(new DemuxOutputStream(project, false)); |
| PrintStream newErr = new PrintStream(new DemuxOutputStream(project, true)); |
| try { |
| System.setOut(newOut); |
| System.setErr(newErr); |
| task.perform(); |
| } finally { |
| System.setOut(initialOut); |
| System.setErr(initialErr); |
| } |
| } |
| } |
| |
| if (task == null) { |
| |
| if (nested == null) { |
| |
| if ( log.isDebugEnabled() ) { |
| log.debug( "Trying to create a data type for tag: " + tagName ); |
| } |
| nested = createDataType( tagName ); |
| } |
| else { |
| if ( log.isDebugEnabled() ) { |
| log.debug( "Created nested property tag: " + tagName ); |
| } |
| } |
| |
| if ( nested != null ) { |
| setObject( nested ); |
| |
| // set the task ID if one is given |
| Object id = getAttributes().remove( "id" ); |
| if ( id != null ) { |
| project.addReference( (String) id, nested ); |
| } |
| |
| // TODO: work out why we always set the name attribute. |
| // See JELLY-105. |
| // try{ |
| // PropertyUtils.setProperty( nested, "name", tagName ); |
| // } |
| // catch (Exception e) { |
| // log.warn( "Caught exception setting nested name: " + tagName, e ); |
| // } |
| |
| // now lets invoke the body |
| String body = getBodyText(); |
| |
| // now lets set any attributes of this tag... |
| setBeanProperties(); |
| |
| // now lets add it to its parent |
| if ( parentObject != null ) { |
| IntrospectionHelper ih = IntrospectionHelper.getHelper( parentObject.getClass() ); |
| try { |
| if (log.isDebugEnabled()) { |
| log.debug("About to set the: " + tagName |
| + " property on: " + safeToString(parentObject) + " to value: " |
| + nested + " with type: " + nested.getClass() |
| ); |
| } |
| |
| ih.storeElement( project, parentObject, nested, tagName.toLowerCase() ); |
| } |
| catch (Exception e) { |
| log.warn( "Caught exception setting nested: " + tagName, e ); |
| } |
| |
| // now try to set the property for good measure |
| // as the storeElement() method does not |
| // seem to call any setter methods of non-String types |
| try { |
| BeanUtils.setProperty( parentObject, tagName, nested ); |
| } |
| catch (Exception e) { |
| log.debug("Caught exception trying to set property: " + tagName + " on: " + safeToString(parentObject)); |
| } |
| } |
| } |
| else { |
| log.warn("Could not convert tag: " + tagName + " into an Ant task, data type or property"); |
| |
| // lets treat this tag as static XML... |
| StaticTag tag = new StaticTag("", tagName, tagName); |
| tag.setParent( getParent() ); |
| tag.setBody( getBody() ); |
| |
| tag.setContext(context); |
| |
| for (Iterator iter = getAttributes().entrySet().iterator(); iter.hasNext();) { |
| Map.Entry entry = (Map.Entry) iter.next(); |
| String name = (String) entry.getKey(); |
| Object value = entry.getValue(); |
| |
| tag.setAttribute(name, value); |
| } |
| |
| tag.doTag(output); |
| } |
| } |
| } |
| |
| |
| // Properties |
| //------------------------------------------------------------------------- |
| public String getTagName() { |
| return this.tagName; |
| } |
| |
| /** Set the object underlying this tag. |
| * |
| * @param object The object. |
| */ |
| public void setObject(Object object) { |
| this.object = object; |
| } |
| |
| public Project getAntProject() { |
| Project project = AntTagLibrary.getProject(context); |
| if (project == null) { |
| throw new NullPointerException("No Ant Project object is available"); |
| } |
| return project; |
| } |
| |
| // Implementation methods |
| //------------------------------------------------------------------------- |
| |
| /** |
| * Sets the properties on the Ant task |
| */ |
| public void setBeanProperties() throws JellyTagException { |
| Object object = getTaskObject(); |
| if ( object != null ) { |
| Map map = getAttributes(); |
| for ( Iterator iter = map.entrySet().iterator(); iter.hasNext(); ) { |
| Map.Entry entry = (Map.Entry) iter.next(); |
| String name = (String) entry.getKey(); |
| Object value = entry.getValue(); |
| setBeanProperty( object, name, value ); |
| } |
| } |
| } |
| |
| public void setAttribute(String name, Object value) { |
| if ( value == null ) { |
| // should we send in null? |
| super.setAttribute( name, "" ); |
| } |
| else { |
| if ( value instanceof Expression ) |
| { |
| super.setAttribute( name, ((Expression) value).evaluateRecurse(context) ); |
| } |
| else |
| { |
| super.setAttribute( name, value.toString() ); |
| } |
| } |
| } |
| |
| public void setBeanProperty(Object object, String name, Object value) throws JellyTagException { |
| if ( log.isDebugEnabled() ) { |
| log.debug( "Setting bean property on: "+ safeToString(object )+ " name: " + name + " value: " + safeToString(value)); |
| } |
| |
| IntrospectionHelper ih = IntrospectionHelper.getHelper( object.getClass() ); |
| |
| if ( value instanceof String ) { |
| try { |
| ih.setAttribute( getAntProject(), object, name.toLowerCase(), (String) value ); |
| return; |
| } |
| catch (Exception e) { |
| // ignore: not a valid property |
| } |
| } |
| |
| try { |
| |
| ih.storeElement( getAntProject(), object, value, name ); |
| } |
| catch (Exception e) { |
| |
| try { |
| // let any exceptions bubble up from here |
| BeanUtils.setProperty( object, name, value ); |
| } |
| catch (IllegalAccessException ex) { |
| throw new JellyTagException(ex); |
| } |
| catch (InvocationTargetException ex) { |
| throw new JellyTagException(ex); |
| } |
| } |
| } |
| |
| |
| /** |
| * Creates a nested object of the given object with the specified name |
| */ |
| public Object createNestedObject(Object object, String name) { |
| Object dataType = null; |
| if ( object != null ) { |
| IntrospectionHelper ih = IntrospectionHelper.getHelper( object.getClass() ); |
| |
| if ( ih != null && ! (object instanceof AntTag)) { |
| try { |
| dataType = ih.createElement( getAntProject(), object, name.toLowerCase() ); |
| } catch (BuildException be) { |
| if (object instanceof Tag) |
| { |
| if (log.isDebugEnabled()) { |
| log.debug("Failed attempt to create an ant datatype for a jelly tag", be); |
| } |
| } else { |
| log.error(be); |
| } |
| } |
| } |
| } |
| |
| if ( dataType == null ) { |
| dataType = createDataType( name ); |
| } |
| |
| return dataType; |
| } |
| |
| public Object createDataType(String name) { |
| |
| Object dataType = null; |
| |
| Class type = (Class) getAntProject().getDataTypeDefinitions().get(name); |
| |
| if ( type != null ) { |
| |
| Constructor ctor = null; |
| boolean noArg = false; |
| |
| // DataType can have a "no arg" constructor or take a single |
| // Project argument. |
| try { |
| ctor = type.getConstructor(new Class[0]); |
| noArg = true; |
| } |
| catch (NoSuchMethodException nse) { |
| try { |
| ctor = type.getConstructor(new Class[] { Project.class }); |
| noArg = false; |
| } catch (NoSuchMethodException nsme) { |
| log.info("datatype '" + name |
| + "' didn't have a constructor with an Ant Project", nsme); |
| } |
| } |
| |
| if (noArg) { |
| dataType = createDataType(ctor, new Object[0], name, "no-arg constructor"); |
| } |
| else { |
| dataType = createDataType(ctor, new Object[] { getAntProject() }, name, "an Ant project"); |
| } |
| if (dataType != null && dataType instanceof DataType) { |
| ((DataType)dataType).setProject( getAntProject() ); |
| } |
| } |
| |
| return dataType; |
| } |
| |
| /** |
| * @return an object create with the given constructor and args. |
| * @param ctor a constructor to use creating the object |
| * @param args the arguments to pass to the constructor |
| * @param name the name of the data type being created |
| * @param argDescription a human readable description of the args passed |
| */ |
| private Object createDataType(Constructor ctor, Object[] args, String name, String argDescription) { |
| try { |
| Object datatype = ctor.newInstance(args); |
| return datatype; |
| } catch (InstantiationException ie) { |
| log.error("datatype '" + name + "' couldn't be created with " + argDescription, ie); |
| } catch (IllegalAccessException iae) { |
| log.error("datatype '" + name + "' couldn't be created with " + argDescription, iae); |
| } catch (InvocationTargetException ite) { |
| log.error("datatype '" + name + "' couldn't be created with " + argDescription, ite); |
| } |
| return null; |
| } |
| |
| /** |
| * @param taskName |
| * @return |
| * @throws JellyTagException |
| */ |
| public Task createTask(String taskName) throws JellyTagException { |
| return createTask( taskName, |
| (Class) getAntProject().getTaskDefinitions().get( taskName ) ); |
| } |
| |
| public Task createTask(String taskName, |
| Class taskType) throws JellyTagException { |
| |
| if (taskType == null) { |
| return null; |
| } |
| |
| Object o = null; |
| try { |
| o = taskType.newInstance(); |
| } catch (InstantiationException e) { |
| throw new JellyTagException(e); |
| } |
| catch (IllegalAccessException e) { |
| throw new JellyTagException(e); |
| } |
| |
| Task task = null; |
| if ( o instanceof Task ) { |
| task = (Task) o; |
| } |
| else { |
| TaskAdapter taskA=new TaskAdapter(); |
| taskA.setProxy( o ); |
| task=taskA; |
| } |
| |
| task.setProject(getAntProject()); |
| task.setTaskName(taskName); |
| |
| return task; |
| } |
| |
| /** |
| * Attempts to look up in the parent hierarchy for a tag that implements the |
| * TaskSource interface, which returns an Ant Task object or that implements |
| * BeanSource interface which creates a bean, |
| * or will return the parent tag, which is also a bean. |
| */ |
| protected Object findBeanAncestor() throws JellyTagException { |
| Tag tag = getParent(); |
| while (tag != null) { |
| if (tag instanceof BeanSource) { |
| BeanSource beanSource = (BeanSource) tag; |
| return beanSource.getBean(); |
| } |
| if (tag instanceof TaskSource) { |
| TaskSource taskSource = (TaskSource) tag; |
| return taskSource.getTaskObject(); |
| } |
| tag = tag.getParent(); |
| } |
| return getParent(); |
| } |
| |
| /** |
| * Walks the hierarchy until it finds a parent TaskSource and returns its source or returns null |
| */ |
| protected Object findParentTaskObject() throws JellyTagException { |
| Tag tag = getParent(); |
| while (tag != null) { |
| if (tag instanceof TaskSource) { |
| TaskSource source = (TaskSource) tag; |
| return source.getTaskObject(); |
| } |
| tag = tag.getParent(); |
| } |
| return null; |
| } |
| |
| private String safeToString(Object o) { |
| if(o==null) return "null"; |
| String r = null; |
| try { |
| r = o.toString(); |
| } catch(Exception ex) {} |
| if(r == null) |
| r = "(object of class " + o.getClass() + ")"; |
| return r; |
| } |
| |
| } |