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