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.wicket.application;
018
019import java.net.URL;
020import java.util.Iterator;
021import java.util.List;
022import java.util.Set;
023import java.util.TreeSet;
024import java.util.concurrent.CopyOnWriteArrayList;
025
026import org.apache.wicket.util.collections.UrlExternalFormComparator;
027import org.apache.wicket.util.lang.Args;
028import org.slf4j.Logger;
029import org.slf4j.LoggerFactory;
030
031/**
032 * A thread safe compound {@link IClassResolver}. Class resolving is done by iterating through all
033 * {@link IClassResolver}s until the class is found. Resource resolving is done by combining the
034 * results of all {@link IClassResolver}s.
035 * 
036 * @author Jesse Long
037 */
038public class CompoundClassResolver implements IClassResolver
039{
040        private static final Logger logger = LoggerFactory.getLogger(CompoundClassResolver.class);
041
042        private final List<IClassResolver> resolvers = new CopyOnWriteArrayList<IClassResolver>();
043
044        /**
045         * {@inheritDoc}
046         * <p>
047         * This implementation iterates through all the {@link IClassResolver} trying to load the class
048         * until the class is found.
049         * 
050         * @param className
051         *            The name of the class to resolve.
052         * @return The {@link Class}, if it is found.
053         * @throws ClassNotFoundException
054         *             If the class was not found
055         */
056        @Override
057        public Class<?> resolveClass(final String className) throws ClassNotFoundException
058        {
059                boolean debugEnabled = logger.isDebugEnabled();
060
061                for (IClassResolver resolver : resolvers)
062                {
063                        try
064                        {
065                                return resolver.resolveClass(className);
066                        }
067                        catch (ClassNotFoundException cnfx)
068                        {
069                                if (debugEnabled)
070                                {
071                                        logger.debug("ClassResolver '{}' cannot find class: '{}'", resolver.getClass()
072                                                .getName(), className);
073                                }
074                        }
075                }
076
077                throw new ClassNotFoundException(className);
078        }
079
080        /**
081         * {@inheritDoc}
082         * <p>
083         * This implementation iterates through all {@link IClassResolver}s added, and combines the
084         * results into one {@link Set} of {@link URL}s, and returns an {@link Iterator} for the set.
085         * {@link URL}s are unique in the set.
086         * 
087         * @param name
088         *            The name of the resource to find.
089         * @return An {@link Iterator} of all the {@link URL}s matching the resource name.
090         */
091        @Override
092        public Iterator<URL> getResources(final String name)
093        {
094                Args.notNull(name, "name");
095
096                Set<URL> urls = new TreeSet<URL>(new UrlExternalFormComparator());
097
098                for (IClassResolver resolver : resolvers)
099                {
100                        Iterator<URL> it = resolver.getResources(name);
101                        while (it.hasNext())
102                        {
103                                URL url = it.next();
104                                urls.add(url);
105                        }
106                }
107
108                return urls.iterator();
109        }
110
111        /**
112         * @return the class loader returned by the first registered IClassResolver. If there is no
113         *  registered IClassResolver then the current thread's context class loader will be returned.
114         */
115        @Override
116        public ClassLoader getClassLoader()
117        {
118                final ClassLoader classLoader;
119                if (resolvers.isEmpty() == false)
120                {
121                        classLoader = resolvers.iterator().next().getClassLoader();
122                }
123                else
124                {
125                        classLoader = Thread.currentThread().getContextClassLoader();
126                }
127                return classLoader;
128        }
129
130        /**
131         * Adds a resolver
132         * 
133         * @param resolver
134         *            The resolver to add
135         * @return {@code this} for chaining
136         */
137        public CompoundClassResolver add(final IClassResolver resolver)
138        {
139                Args.notNull(resolver, "resolver");
140                resolvers.add(resolver);
141                return this;
142        }
143
144        /**
145         * Removes a resolver
146         * 
147         * @param resolver
148         *            The resolver to remove
149         * @return {@code this} for chaining
150         */
151        public CompoundClassResolver remove(final IClassResolver resolver)
152        {
153                resolvers.remove(resolver);
154                return this;
155        }
156}