1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
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
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
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
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 }