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
015package org.apache.tapestry.spec;
016
017import java.util.Collections;
018import java.util.HashMap;
019import java.util.Iterator;
020import java.util.Map;
021
022import org.apache.commons.logging.Log;
023import org.apache.commons.logging.LogFactory;
024import org.apache.hivemind.ApplicationRuntimeException;
025import org.apache.hivemind.ClassResolver;
026import org.apache.hivemind.util.PropertyUtils;
027import org.apache.tapestry.Tapestry;
028import org.apache.tapestry.coerce.ValueConverter;
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
038public class ExtensionSpecification extends LocatablePropertyHolder implements
039        IExtensionSpecification
040{
041    private static final Log LOG = LogFactory.getLog(ExtensionSpecification.class);
042
043    private String _className;
044
045    protected Map _configuration = new HashMap();
046
047    private boolean _immediate;
048
049    /** @since 4.0 */
050
051    private ClassResolver _resolver;
052
053    /** @since 4.0 */
054    private ValueConverter _converter;
055
056    /** @since 4.0 */
057    public ExtensionSpecification(ClassResolver resolver, ValueConverter valueConverter)
058    {
059        _resolver = resolver;
060        _converter = valueConverter;
061    }
062
063    public String getClassName()
064    {
065        return _className;
066    }
067
068    public void setClassName(String className)
069    {
070        _className = className;
071    }
072
073    public void addConfiguration(String propertyName, String value)
074    {
075        if (_configuration.containsKey(propertyName))
076            throw new IllegalArgumentException(Tapestry.format(
077                    "ExtensionSpecification.duplicate-property",
078                    this,
079                    propertyName));
080
081        _configuration.put(propertyName, value);
082    }
083
084    /**
085     * Returns an immutable Map of the configuration; keyed on property name, with values as
086     * properties to assign.
087     */
088
089    public Map getConfiguration()
090    {
091        return Collections.unmodifiableMap(_configuration);
092    }
093
094    /**
095     * Invoked to instantiate an instance of the extension and return it. It also configures
096     * properties of the extension.
097     */
098
099    public Object instantiateExtension()
100    {
101        if (LOG.isDebugEnabled())
102            LOG.debug("Instantiating extension class " + _className + ".");
103
104        Class extensionClass = null;
105        Object result = null;
106
107        try
108        {
109            extensionClass = _resolver.findClass(_className);
110        }
111        catch (Exception ex)
112        {
113            throw new ApplicationRuntimeException(Tapestry.format(
114                    "ExtensionSpecification.bad-class",
115                    _className), getLocation(), ex);
116        }
117
118        result = instantiateInstance(extensionClass, result);
119
120        initializeProperties(result);
121
122        return result;
123    }
124
125    private void initializeProperties(Object extension)
126    {
127
128        Iterator i = _configuration.entrySet().iterator();
129        while (i.hasNext())
130        {
131            Map.Entry entry = (Map.Entry) i.next();
132
133            String propertyName = (String) entry.getKey();
134            String textValue = (String) entry.getValue();
135
136            try
137            {
138                Class propertyType = PropertyUtils.getPropertyType(extension, propertyName);
139
140                Object objectValue = _converter.coerceValue(textValue, propertyType);
141
142                PropertyUtils.write(extension, propertyName, objectValue);
143            }
144            catch (Exception ex)
145            {
146                throw new ApplicationRuntimeException(ex.getMessage(), getLocation(), ex);
147            }
148        }
149    }
150
151    private Object instantiateInstance(Class extensionClass, Object result)
152    {
153        try
154        {
155            result = extensionClass.newInstance();
156        }
157        catch (Exception ex)
158        {
159            throw new ApplicationRuntimeException(ex.getMessage(), getLocation(), ex);
160        }
161
162        return result;
163    }
164
165    public String toString()
166    {
167        StringBuffer buffer = new StringBuffer("ExtensionSpecification@");
168        buffer.append(Integer.toHexString(hashCode()));
169        buffer.append('[');
170        buffer.append(_className);
171
172        if (_configuration != null)
173        {
174            buffer.append(' ');
175            buffer.append(_configuration);
176        }
177
178        buffer.append(']');
179
180        return buffer.toString();
181    }
182
183    /**
184     * Returns true if the extensions should be instantiated immediately after the containing
185     * {@link org.apache.tapestry.spec.LibrarySpecification}if parsed. Non-immediate extensions are
186     * instantiated only as needed.
187     */
188
189    public boolean isImmediate()
190    {
191        return _immediate;
192    }
193
194    public void setImmediate(boolean immediate)
195    {
196        _immediate = immediate;
197    }
198
199}