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