001 /* ====================================================================
002 * The Apache Software License, Version 1.1
003 *
004 * Copyright (c) 2003 The Apache Software Foundation. All rights
005 * reserved.
006 *
007 * Redistribution and use in source and binary forms, with or without
008 * modification, are permitted provided that the following conditions
009 * are met:
010 *
011 * 1. Redistributions of source code must retain the above copyright
012 * notice, this list of conditions and the following disclaimer.
013 *
014 * 2. Redistributions in binary form must reproduce the above copyright
015 * notice, this list of conditions and the following disclaimer in
016 * the documentation and/or other materials provided with the
017 * distribution.
018 *
019 * 3. The end-user documentation included with the redistribution,
020 * if any, must include the following acknowledgment:
021 * "This product includes software developed by the
022 * Apache Software Foundation (http://www.apache.org/)."
023 * Alternately, this acknowledgment may appear in the software itself,
024 * if and wherever such third-party acknowledgments normally appear.
025 *
026 * 4. The names "The Jakarta Project", "Commons", and "Apache Software
027 * Foundation" must not be used to endorse or promote products derived
028 * from this software without prior written permission. For written
029 * permission, please contact apache@apache.org.
030 *
031 * 5. Products derived from this software may not be called "Apache",
032 * nor may "Apache" appear in their name, without prior written
033 * permission of the Apache Software Foundation.
034 *
035 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
036 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
037 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
038 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
039 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
040 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
041 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
042 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
043 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
044 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
045 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
046 * SUCH DAMAGE.
047 * ====================================================================
048 *
049 * This software consists of voluntary contributions made by many
050 * individuals on behalf of the Apache Software Foundation. For more
051 * information on the Apache Software Foundation, please see
052 * <http://www.apache.org/>.
053 *
054 */
055 package org.jpu.patterns.factory;
056
057 import java.util.Iterator;
058 import java.util.List;
059
060 import org.apache.commons.lang.StringUtils;
061 import org.apache.commons.logging.Log;
062 import org.apache.commons.logging.LogFactory;
063 import org.jpu.patterns.common.JPUStringUtils;
064 import org.jpu.patterns.factory.IPrototypeFactory;
065 import org.jpu.patterns.factory.PrototypeFactoryException;
066
067 /**
068 * Implementation of {@link org.jpu.patterns.factory.IPrototypeFactory.IConfigStrategy} for property-file-based configuration.
069 * A prototype list should comply with the following syntax:
070 * <pre>
071 * <protolist> = [<protospec>{;<protospec>}*]
072 * <protospec> = [<name1>[,<name2>[,<name3>[,<nameN>]]]=<classname>]
073 * </pre>
074 * where <name1> ... <nameN> are the names under which the prototype is keyed and <classname> is the
075 * full name of the prototype implementation class, or of the {@link org.jpu.patterns.factory.IPrototypeFactory.IInstantiator} to which
076 * instantiation should be delegated. Every class listed must have an accessible no-arg constructor.
077 * <p>
078 * The source of the <protospec> is by default a System property named
079 * "<factoryClassName>.prototypes" where <factoryClassName> is the full class name of the factory
080 * being configured; but in the vast majority of cases the subclass will override the following
081 * method
082 * <pre>
083 * <code>
084 * protected String fetchPrototypeSpec();
085 * </code>
086 * </pre>
087 * and have it return the <protospec> from whichever configuration source the application employs.
088 * <p>
089 * To specify a delimeter to use instead of ";", override the following method:
090 * <pre>
091 * <code>
092 * protected String getPrototypeDeclarationSeparator();
093 * </code>
094 * </pre>
095 *
096 */
097 public class PropertyFactoryConfigStrategy implements IPrototypeFactory.IConfigStrategy {
098 protected PropertyFactoryConfigStrategy(IPrototypeFactory fact) {
099 _factory = fact;
100 }
101
102 public IPrototypeFactory getFactory() {
103 return _factory;
104 }
105
106 public void reconfigureIfNecessary() {}
107
108 public void configure() {
109 boolean locked = getFactory().lock();
110 try {
111 // Remove all prototypes first.
112 getFactory().clear();
113 _log.debug( "Loading statically configured prototypes." );
114 String value = getPrototypeSpec();
115 if ( ! StringUtils.isEmpty(value) ) {
116 List protoDefs = JPUStringUtils.split(value, getPrototypeDeclarationSeparator());
117 Iterator it = protoDefs.iterator();
118 while ( it.hasNext() ) {
119 String protoDef = (String)it.next();
120 processOneConfigProtoDef(protoDef);
121 }
122 }
123 }
124 finally {
125 getFactory().unlock(locked);
126 }
127 }
128
129 /**
130 * Returns whether an error is logged if the default configuration value is unavailable and the fallback is used in its place.
131 * Default is <code>true</code>, but subclasses are free to override.
132 *
133 * @return <code>true</code> if an error should be logged, <code>false</code> otherwise.
134 *
135 */
136 protected boolean logErrorIfConfigurationUnavailable() {
137 return true;
138 }
139
140 /**
141 * Returns the prototype spec that is used if none is found in the configuration database. Default is <code>null</code>, but
142 * subclasses are free to override.
143 *
144 * @return The fallback configuration (<code>null</code> by default).
145 *
146 */
147 protected String getFallbackPrototypeSpec() {
148 return null;
149 }
150
151 /**
152 * Returns the string that acts as a separator between <protospec>'s. Default is ";", but subclasses are free to override.
153 */
154 protected String getPrototypeDeclarationSeparator() {
155 return ";";
156 }
157
158 /**
159 * Returns the string that acts as a separator between successive prototype names. Default is ",", but subclasses are free to override.
160 */
161 protected String getPrototypeNameSeparator() {
162 return ",";
163 }
164
165 /**
166 * Returns the string that acts as a separator between the list of prototype names and the name of the prototype class.
167 * Default is "=", but subclasses are free to override.
168 */
169 protected String getAssignmentString() {
170 return "=";
171 }
172
173 /**
174 * Processes a single prototype declaration. Adds prototypes to the factory as necessary.
175 */
176 protected void processOneConfigProtoDef(String protoDef) {
177 try {
178 if ( _log.isDebugEnabled() ) {
179 _log.debug( "Processing protoDef '" + protoDef + "'" );
180 }
181 List keyValuePair = JPUStringUtils.split( protoDef, getAssignmentString() );
182 if ( keyValuePair.size() == 1 ) {
183 keyValuePair.add(0, "");
184 }
185 if ( keyValuePair.size() == 2 ) {
186 String aliases = (String)keyValuePair.get(0);
187 String className = (String)keyValuePair.get(1);
188
189 List aliasesList = JPUStringUtils.split( aliases, getPrototypeNameSeparator() );
190 Object[] aliasesArray = aliasesList.toArray( new Object[0] );
191
192 if ( _log.isDebugEnabled() ) {
193 _log.debug( "Trying to load class + '" + className + "'" );
194 }
195 Object prototype = classNameToInstance(className);
196
197 if ( prototype instanceof IPrototypeFactory.IInstantiator ) {
198 IPrototypeFactory.IInstantiator instantiator = (IPrototypeFactory.IInstantiator)prototype;
199 if ( instantiator.forPrototypeOnly() ) {
200 prototype = instantiator.create( new FactoryOptions() );
201 getFactory().initializePrototype(prototype, IPrototypeFactory.IInitializer.INSTANTIATOR, null);
202 }
203 }
204 getFactory().putPrototype(prototype, aliasesArray);
205 }
206 else {
207 throw new PrototypeFactoryException( "Invalid prototype definition '" + protoDef + "'" );
208 }
209 }
210 catch( Throwable e )
211 {
212 // Log as warning and move on.
213 _log.warn( "Unable to load prototype for protoDef '" + protoDef + "' for the following reason: " + e );
214 }
215 }
216
217 /**
218 * Returns a new instance of the prototype whose name is passed. Default implementation is
219 * essentially "<code>Thread.currentThread().getContextClassLoader().loadClass(className).newInstance()</code>", but
220 * subclasses are free to override.
221 */
222 protected Object classNameToInstance(String className) throws Throwable {
223 Class theClass = Thread.currentThread().getContextClassLoader().loadClass(className);
224
225 if ( _log.isDebugEnabled() ) {
226 _log.debug( "Trying to instantiate class + '" + className + "'" );
227 }
228 Object prototype = theClass.newInstance();
229 return prototype;
230 }
231
232 protected String getPrototypeSpec( ) {
233 String result = fetchPrototypeSpec();
234 if ( result == null ) {
235 result = getFallbackPrototypeSpec();
236 if ( logErrorIfConfigurationUnavailable() ) {
237 _log.error( "Required prototype configuration of factory class '" + getFactory().getClass().getName() + "' is unavailable. Proceeding with fallback configuration '" + result + "'" );
238 }
239 }
240 return result;
241 }
242
243 /**
244 * This method fetches the factory's prototype list from the configuration source. The default implementation
245 * is as follows:
246 * <pre>
247 * <code>
248 * return System.getProperty( getFactory().getClass().getName() + ".prototypes", "" );
249 * </code>
250 * </pre>
251 * However, in the majority of cases the subclass will override this to hook into whichever configuration source the application uses.
252 */
253 protected String fetchPrototypeSpec() {
254 return System.getProperty( getFactory().getClass().getName() + ".prototypes", "" );
255 }
256
257 private IPrototypeFactory _factory = null;
258 private static Log _log = LogFactory.getLog( PropertyFactoryConfigStrategy.class );
259 }
260