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 }