View Javadoc

1   /*
2    * Copyright 1999-2004 The Apache Software Foundation
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.apache.commons.chain;
17  
18  import java.util.HashMap;
19  import java.util.Iterator;
20  import java.util.Map;
21  import org.apache.commons.chain.impl.CatalogFactoryBase;
22  
23  import org.apache.commons.logging.Log;
24  import org.apache.commons.logging.LogFactory;
25  
26  /***
27   * <p>A {@link CatalogFactory} is a class used to store and retrieve
28   * {@link Catalog}s.  The factory allows for a default {@link Catalog}
29   * as well as {@link Catalog}s stored with a name key.  Follows the
30   * Factory pattern (see GoF).</p>
31   *
32   * <p>The base <code>CatalogFactory</code> implementation also implements
33   * a resolution mechanism which allows lookup of a command based on a single
34   * String which encodes both the catalog and command names.</p>
35   *
36   * @author Sean Schofield
37   * @version $Revision: 411893 $ $Date: 2006-06-05 19:59:05 +0100 (Mon, 05 Jun 2006) $
38   */
39  
40  public abstract class CatalogFactory {
41  
42  
43      /***
44       * <p>Values passed to the <code>getCommand(String)</code> method should
45       * use this as the delimiter between the "catalog" name and the "command"
46       * name.</p>
47       */
48      public static final String DELIMITER = ":";
49  
50  
51      // --------------------------------------------------------- Public Methods
52  
53  
54      /***
55       * <p>Gets the default instance of Catalog associated with the factory
56       * (if any); otherwise, return <code>null</code>.</p>
57       *
58       * @return the default Catalog instance
59       */
60      public abstract Catalog getCatalog();
61  
62  
63      /***
64       * <p>Sets the default instance of Catalog associated with the factory.</p>
65       *
66       * @param catalog the default Catalog instance
67       */
68      public abstract void setCatalog(Catalog catalog);
69  
70  
71      /***
72       * <p>Retrieves a Catalog instance by name (if any); otherwise
73       * return <code>null</code>.</p>
74       *
75       * @param name the name of the Catalog to retrieve
76       * @return the specified Catalog
77       */
78      public abstract Catalog getCatalog(String name);
79  
80  
81      /***
82       * <p>Adds a named instance of Catalog to the factory (for subsequent
83       * retrieval later).</p>
84       *
85       * @param name the name of the Catalog to add
86       * @param catalog the Catalog to add
87       */
88      public abstract void addCatalog(String name, Catalog catalog);
89  
90  
91      /***
92       * <p>Return an <code>Iterator</code> over the set of named
93       * {@link Catalog}s known to this {@link CatalogFactory}.
94       * If there are no known catalogs, an empty Iterator is returned.</p>
95       * @return An Iterator of the names of the Catalogs known by this factory.
96       */
97      public abstract Iterator getNames();
98  
99  
100     /***
101      * <p>Return a <code>Command</code> based on the given commandID.</p>
102      *
103      * <p>At this time, the structure of commandID is relatively simple:  if the
104      * commandID contains a DELIMITER, treat the segment of the commandID
105      * up to (but not including) the DELIMITER as the name of a catalog, and the
106      * segment following the DELIMITER as a command name within that catalog.
107      * If the commandID contains no DELIMITER, treat the commandID as the name
108      * of a command in the default catalog.</p>
109      *
110      * <p>To preserve the possibility of future extensions to this lookup
111      * mechanism, the DELIMITER string should be considered reserved, and
112      * should not be used in command names.  commandID values which contain
113      * more than one DELIMITER will cause an
114      * <code>IllegalArgumentException</code> to be thrown.</p>
115      *
116      * @param commandID the identifier of the command to return
117      * @return the command located with commandID, or <code>null</code>
118      *  if either the command name or the catalog name cannot be resolved
119      * @throws IllegalArgumentException if the commandID contains more than
120      *  one DELIMITER
121      *
122      * @since Chain 1.1
123      */
124     public Command getCommand(String commandID) {
125 
126         String commandName = commandID;
127         String catalogName = null;
128         Catalog catalog = null;
129 
130         if (commandID != null) {
131             int splitPos = commandID.indexOf(DELIMITER);
132             if (splitPos != -1) {
133                 catalogName = commandID.substring(0, splitPos);
134                 commandName = commandID.substring(splitPos + DELIMITER.length());
135                 if (commandName.indexOf(DELIMITER) != -1) {
136                     throw new IllegalArgumentException("commandID [" +
137                                                        commandID +
138                                                        "] has too many delimiters (reserved for future use)");
139                 }
140             }
141         }
142 
143         if (catalogName != null) {
144             catalog = this.getCatalog(catalogName);
145             if (catalog == null) {
146                 Log log = LogFactory.getLog(CatalogFactory.class);
147                 log.warn("No catalog found for name: " + catalogName + ".");
148                 return null;
149             }
150         } else {
151             catalog = this.getCatalog();
152             if (catalog == null) {
153                 Log log = LogFactory.getLog(CatalogFactory.class);
154                 log.warn("No default catalog found.");
155                 return null;
156             }
157         }
158 
159         return catalog.getCommand(commandName);
160 
161     }
162 
163 
164     // ------------------------------------------------------- Static Variables
165 
166 
167     /***
168      * <p>The set of registered {@link CatalogFactory} instances,
169      * keyed by the relevant class loader.</p>
170      */
171     private static Map factories = new HashMap();
172 
173 
174     // -------------------------------------------------------- Static Methods
175 
176 
177     /***
178      * <p>Return the singleton {@link CatalogFactory} instance
179      * for the relevant <code>ClassLoader</code>.  For applications
180      * that use a thread context class loader (such as web applications
181      * running inside a servet container), this will return a separate
182      * instance for each application, even if this class is loaded from
183      * a shared parent class loader.</p>
184      *
185      * @return the per-application singleton instance of {@link CatalogFactory}
186      */
187     public static CatalogFactory getInstance() {
188 
189         CatalogFactory factory = null;
190         ClassLoader cl = getClassLoader();
191         synchronized (factories) {
192             factory = (CatalogFactory) factories.get(cl);
193             if (factory == null) {
194                 factory = new CatalogFactoryBase();
195                 factories.put(cl, factory);
196             }
197         }
198         return factory;
199 
200     }
201 
202 
203     /***
204      * <p>Clear all references to registered catalogs, as well as to the
205      * relevant class loader.  This method should be called, for example,
206      * when a web application utilizing this class is removed from
207      * service, to allow for garbage collection.</p>
208      */
209     public static void clear() {
210 
211         synchronized (factories) {
212             factories.remove(getClassLoader());
213         }
214 
215     }
216 
217 
218     // ------------------------------------------------------- Private Methods
219 
220 
221     /***
222      * <p>Return the relevant <code>ClassLoader</code> to use as a Map key
223      * for this request.  If there is a thread context class loader, return
224      * that; otherwise, return the class loader that loaded this class.</p>
225      */
226     private static ClassLoader getClassLoader() {
227 
228         ClassLoader cl = Thread.currentThread().getContextClassLoader();
229         if (cl == null) {
230             cl = CatalogFactory.class.getClassLoader();
231         }
232         return cl;
233 
234     }
235 
236 }