001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package org.apache.commons.chain; 018 019 import java.util.HashMap; 020 import java.util.Iterator; 021 import java.util.Map; 022 import org.apache.commons.chain.impl.CatalogFactoryBase; 023 024 import org.apache.commons.logging.Log; 025 import org.apache.commons.logging.LogFactory; 026 027 /** 028 * <p>A {@link CatalogFactory} is a class used to store and retrieve 029 * {@link Catalog}s. The factory allows for a default {@link Catalog} 030 * as well as {@link Catalog}s stored with a name key. Follows the 031 * Factory pattern (see GoF).</p> 032 * 033 * <p>The base <code>CatalogFactory</code> implementation also implements 034 * a resolution mechanism which allows lookup of a command based on a single 035 * String which encodes both the catalog and command names.</p> 036 * 037 * @author Sean Schofield 038 * @version $Revision: 480477 $ $Date: 2006-11-29 08:34:52 +0000 (Wed, 29 Nov 2006) $ 039 */ 040 041 public abstract class CatalogFactory { 042 043 044 /** 045 * <p>Values passed to the <code>getCommand(String)</code> method should 046 * use this as the delimiter between the "catalog" name and the "command" 047 * name.</p> 048 */ 049 public static final String DELIMITER = ":"; 050 051 052 // --------------------------------------------------------- Public Methods 053 054 055 /** 056 * <p>Gets the default instance of Catalog associated with the factory 057 * (if any); otherwise, return <code>null</code>.</p> 058 * 059 * @return the default Catalog instance 060 */ 061 public abstract Catalog getCatalog(); 062 063 064 /** 065 * <p>Sets the default instance of Catalog associated with the factory.</p> 066 * 067 * @param catalog the default Catalog instance 068 */ 069 public abstract void setCatalog(Catalog catalog); 070 071 072 /** 073 * <p>Retrieves a Catalog instance by name (if any); otherwise 074 * return <code>null</code>.</p> 075 * 076 * @param name the name of the Catalog to retrieve 077 * @return the specified Catalog 078 */ 079 public abstract Catalog getCatalog(String name); 080 081 082 /** 083 * <p>Adds a named instance of Catalog to the factory (for subsequent 084 * retrieval later).</p> 085 * 086 * @param name the name of the Catalog to add 087 * @param catalog the Catalog to add 088 */ 089 public abstract void addCatalog(String name, Catalog catalog); 090 091 092 /** 093 * <p>Return an <code>Iterator</code> over the set of named 094 * {@link Catalog}s known to this {@link CatalogFactory}. 095 * If there are no known catalogs, an empty Iterator is returned.</p> 096 * @return An Iterator of the names of the Catalogs known by this factory. 097 */ 098 public abstract Iterator getNames(); 099 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 }