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 */
017package org.apache.commons.chain;
018
019import java.util.Iterator;
020import java.util.concurrent.ConcurrentHashMap;
021
022import org.apache.commons.chain.impl.CatalogFactoryBase;
023import org.slf4j.Logger;
024import org.slf4j.LoggerFactory;
025
026/**
027 * A {@link CatalogFactory} is a class used to store and retrieve
028 * {@link Catalog}s. The factory allows for a default {@link Catalog}
029 * as well as {@link Catalog}s stored with a name key. Follows the
030 * Factory pattern (see GoF).
031 *
032 * <p>The base {@code CatalogFactory} implementation also implements
033 * a resolution mechanism which allows lookup of a command based on a single
034 * String which encodes both the catalog and command names.</p>
035 *
036 * @param <C> Type of the context associated with this command
037 *
038 * @author Sean Schofield
039 * @version $Revision$ $Date$
040 */
041public abstract class CatalogFactory<C extends Context> {
042
043    // ------------------------------------------------------- Static Variables
044
045    /**
046     * Values passed to the {@code getCommand(String)} method should
047     * use this as the delimiter between the "catalog" name and the "command"
048     * name.
049     */
050    public static final String DELIMITER = ":";
051
052    /**
053     * The set of registered {@link CatalogFactory} instances,
054     * keyed by the relevant class loader.
055     */
056    private static final ConcurrentHashMap<ClassLoader, CatalogFactory<?>> FACTORIES = new ConcurrentHashMap<>();
057
058    // --------------------------------------------------------- Public Methods
059
060    /**
061     * Gets the default instance of Catalog associated with the factory
062     * (if any); otherwise, return {@code null}.
063     *
064     * @return the default Catalog instance
065     */
066    public abstract Catalog<C> getCatalog();
067
068    /**
069     * Sets the default instance of Catalog associated with the factory.
070     *
071     * @param catalog the default Catalog instance
072     */
073    public abstract void setCatalog(Catalog<C> catalog);
074
075    /**
076     * Retrieves a Catalog instance by name (if any); otherwise
077     * return {@code null}.
078     *
079     * @param name the name of the Catalog to retrieve
080     *
081     * @return the specified Catalog
082     */
083    public abstract Catalog<C> getCatalog(String name);
084
085    /**
086     * Adds a named instance of Catalog to the factory (for subsequent
087     * retrieval later).
088     *
089     * @param name the name of the Catalog to add
090     * @param catalog the Catalog to add
091     */
092    public abstract void addCatalog(String name, Catalog<C> catalog);
093
094    /**
095     * Return an {@code Iterator} over the set of named
096     * {@link Catalog}s known to this {@link CatalogFactory}.
097     * If there are no known catalogs, an empty Iterator is returned.
098     *
099     * @return An Iterator of the names of the Catalogs known by this factory.
100     */
101    public abstract Iterator<String> getNames();
102
103    /**
104     * The Default-Constructor for this class.
105     */
106    public CatalogFactory() {
107    }
108
109    /**
110     * Return a {@code Command} based on the given commandID.
111     *
112     * <p>At this time, the structure of commandID is relatively simple: if the
113     * commandID contains a DELIMITER, treat the segment of the commandID
114     * up to (but not including) the DELIMITER as the name of a catalog, and the
115     * segment following the DELIMITER as a command name within that catalog.
116     * If the commandID contains no DELIMITER, treat the commandID as the name
117     * of a command in the default catalog.</p>
118     *
119     * <p>To preserve the possibility of future extensions to this lookup
120     * mechanism, the DELIMITER string should be considered reserved, and
121     * should not be used in command names. commandID values which contain
122     * more than one DELIMITER will cause an {@code IllegalArgumentException}
123     * to be thrown.</p>
124     *
125     * @param <CMD> the expected {@link Command} type to be returned
126     * @param commandID the identifier of the command to return
127     *
128     * @return the command located with commandID, or {@code null}
129     *         if either the command name or the catalog name cannot be
130     *         resolved
131     *
132     * @throws IllegalArgumentException if the commandID contains more than
133     *         one DELIMITER
134     *
135     * @since Chain 1.1
136     */
137    public <CMD extends Command<C>> CMD getCommand(String commandID) {
138        String commandName = commandID;
139        String catalogName = null;
140        Catalog<C> catalog = null;
141
142        if (commandID != null) {
143            int splitPos = commandID.indexOf(DELIMITER);
144            if (splitPos != -1) {
145                catalogName = commandID.substring(0, splitPos);
146                commandName = commandID.substring(splitPos + DELIMITER.length());
147                if (commandName.contains(DELIMITER)) {
148                    throw new IllegalArgumentException("commandID ["
149                            + commandID + "] has too many delimiters (reserved for future use)");
150                }
151            }
152        }
153
154        if (catalogName != null) {
155            catalog = this.getCatalog(catalogName);
156            if (catalog == null) {
157                Logger logger = LoggerFactory.getLogger(CatalogFactory.class);
158                logger.warn("No catalog found for name: {}.", catalogName);
159                return null;
160            }
161        } else {
162            catalog = this.getCatalog();
163            if (catalog == null) {
164                Logger logger = LoggerFactory.getLogger(CatalogFactory.class);
165                logger.warn("No default catalog found.");
166                return null;
167            }
168        }
169
170        return catalog.getCommand(commandName);
171    }
172
173    // -------------------------------------------------------- Static Methods
174
175    /**
176     * Return the singleton {@link CatalogFactory} instance
177     * for the relevant {@code ClassLoader}. For applications
178     * that use a thread context class loader (such as web applications
179     * running inside a servet container), this will return a separate
180     * instance for each application, even if this class is loaded from
181     * a shared parent class loader.
182     *
183     * @param <C> Type of the context associated with this command
184     *
185     * @return the per-application singleton instance of {@link CatalogFactory}
186     */
187    public static <C extends Context> CatalogFactory<C> getInstance() {
188        ClassLoader cl = getClassLoader();
189
190        @SuppressWarnings("unchecked")
191        CatalogFactory<C> ret = (CatalogFactory<C>) FACTORIES.computeIfAbsent(cl, k -> new CatalogFactoryBase<>());
192        return ret;
193    }
194
195    /**
196     * Clear all references to registered catalogs, as well as to the
197     * relevant class loader. This method should be called, for example,
198     * when a web application utilizing this class is removed from
199     * service, to allow for garbage collection.
200     */
201    public static void clear() {
202        FACTORIES.remove(getClassLoader());
203    }
204
205    // ------------------------------------------------------- Private Methods
206
207    /**
208     * Return the relevant {@code ClassLoader} to use as a Map key
209     * for this request. If there is a thread context class loader, return
210     * that; otherwise, return the class loader that loaded this class.
211     *
212     * @return the relevant {@code ClassLoader} to use as a Map key for this request
213     */
214    private static ClassLoader getClassLoader() {
215        ClassLoader cl = Thread.currentThread().getContextClassLoader();
216        if (cl == null) {
217            cl = CatalogFactory.class.getClassLoader();
218        }
219        return cl;
220    }
221}