001    // Copyright 2004, 2005 The Apache Software Foundation
002    //
003    // Licensed under the Apache License, Version 2.0 (the "License");
004    // you may not use this file except in compliance with the License.
005    // You may obtain a copy of the License at
006    //
007    //     http://www.apache.org/licenses/LICENSE-2.0
008    //
009    // Unless required by applicable law or agreed to in writing, software
010    // distributed under the License is distributed on an "AS IS" BASIS,
011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012    // See the License for the specific language governing permissions and
013    // limitations under the License.
014    
015    package org.apache.tapestry.spec;
016    
017    import org.apache.commons.logging.Log;
018    import org.apache.commons.logging.LogFactory;
019    import org.apache.hivemind.ApplicationRuntimeException;
020    import org.apache.hivemind.ClassResolver;
021    import org.apache.hivemind.util.PropertyUtils;
022    import org.apache.tapestry.Tapestry;
023    import org.apache.tapestry.coerce.ValueConverter;
024    
025    import java.util.Collections;
026    import java.util.HashMap;
027    import java.util.Iterator;
028    import java.util.Map;
029    
030    /**
031     * Defines an "extension", which is much like a helper bean, but is part of a library or application
032     * specification (and has the same lifecycle as the application).
033     * 
034     * @author Howard Lewis Ship
035     * @since 2.2
036     */
037    
038    public class ExtensionSpecification extends LocatablePropertyHolder implements IExtensionSpecification
039    {
040        private static final Log LOG = LogFactory.getLog(ExtensionSpecification.class);
041        
042        protected Map _configuration = new HashMap();
043        
044        private String _className;
045        
046        private boolean _immediate;
047    
048        /** @since 4.0 */
049        private ClassResolver _resolver;
050    
051        /** @since 4.0 */
052        private ValueConverter _converter;
053    
054        /**
055         * Creates a new instance which will use the specified resolver to resolve classes and converter
056         * to coerce values to their desired type.
057         *
058         * @param resolver
059         *          The class resolver used to resolve classes safely in a servlet environment.
060         * @param valueConverter
061         *          Converter used to coerce values.
062         */
063        public ExtensionSpecification(ClassResolver resolver, ValueConverter valueConverter)
064        {
065            _resolver = resolver;
066            _converter = valueConverter;
067        }
068    
069        public String getClassName()
070        {
071            return _className;
072        }
073    
074        public void setClassName(String className)
075        {
076            _className = className;
077        }
078    
079        public void addConfiguration(String propertyName, String value)
080        {
081            if (_configuration.containsKey(propertyName))
082                throw new IllegalArgumentException(Tapestry.format("ExtensionSpecification.duplicate-property", this, propertyName));
083    
084            _configuration.put(propertyName, value);
085        }
086    
087        /**
088         * Returns an immutable Map of the configuration; keyed on property name, with values as
089         * properties to assign.
090         */
091    
092        public Map getConfiguration()
093        {
094            return Collections.unmodifiableMap(_configuration);
095        }
096    
097        /**
098         * Invoked to instantiate an instance of the extension and return it. It also configures
099         * properties of the extension.
100         */
101    
102        public Object instantiateExtension()
103        {
104            if (LOG.isDebugEnabled())
105                LOG.debug("Instantiating extension class " + _className + ".");
106    
107            Class extensionClass = null;
108            Object result = null;
109    
110            try
111            {
112                extensionClass = _resolver.findClass(_className);
113            }
114            catch (Exception ex)
115            {
116                throw new ApplicationRuntimeException(Tapestry.format(
117                        "ExtensionSpecification.bad-class",
118                        _className), getLocation(), ex);
119            }
120    
121            result = instantiateInstance(extensionClass, result);
122    
123            initializeProperties(result);
124    
125            return result;
126        }
127    
128        private void initializeProperties(Object extension)
129        {
130    
131            Iterator i = _configuration.entrySet().iterator();
132            while (i.hasNext())
133            {
134                Map.Entry entry = (Map.Entry) i.next();
135    
136                String propertyName = (String) entry.getKey();
137                String textValue = (String) entry.getValue();
138    
139                try
140                {
141                    Class propertyType = PropertyUtils.getPropertyType(extension, propertyName);
142    
143                    Object objectValue = _converter.coerceValue(textValue, propertyType);
144    
145                    PropertyUtils.write(extension, propertyName, objectValue);
146                }
147                catch (Exception ex)
148                {
149                    throw new ApplicationRuntimeException(ex.getMessage(), getLocation(), ex);
150                }
151            }
152        }
153    
154        private Object instantiateInstance(Class extensionClass, Object result)
155        {
156            Object returnResult = result;
157            try
158            {
159                returnResult = extensionClass.newInstance();
160            }
161            catch (Exception ex)
162            {
163                throw new ApplicationRuntimeException(ex.getMessage(), getLocation(), ex);
164            }
165    
166            return returnResult;
167        }
168    
169        public String toString()
170        {
171            StringBuffer buffer = new StringBuffer("ExtensionSpecification@");
172            buffer.append(Integer.toHexString(hashCode()));
173            buffer.append('[');
174            buffer.append(_className);
175    
176            if (_configuration != null)
177            {
178                buffer.append(' ');
179                buffer.append(_configuration);
180            }
181    
182            buffer.append(']');
183    
184            return buffer.toString();
185        }
186    
187        /**
188         * Returns true if the extensions should be instantiated immediately after the containing
189         * {@link org.apache.tapestry.spec.LibrarySpecification}if parsed. Non-immediate extensions are
190         * instantiated only as needed.
191         */
192    
193        public boolean isImmediate()
194        {
195            return _immediate;
196        }
197    
198        public void setImmediate(boolean immediate)
199        {
200            _immediate = immediate;
201        }
202    
203    }