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 }