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.lang.ref.WeakReference;
020import java.net.URL;
021import java.util.Enumeration;
022import java.util.Iterator;
023import java.util.Set;
024import java.util.TreeSet;
025import java.util.concurrent.ConcurrentHashMap;
026import java.util.concurrent.ConcurrentMap;
027
028import org.apache.wicket.Application;
029import org.apache.wicket.WicketRuntimeException;
030import org.apache.wicket.util.collections.UrlExternalFormComparator;
031
032/**
033 * An abstract implementation of a {@link IClassResolver} which uses a {@link ClassLoader} for
034 * resolving classes.
035 * 
036 * @see org.apache.wicket.settings.ApplicationSettings#getClassResolver()
037 * 
038 * @author Juergen Donnerstag
039 * @author Jonathan Locke
040 */
041public abstract class AbstractClassResolver implements IClassResolver
042{
043        /**
044         * Usually class loaders implement more efficient caching strategies than we could possibly do,
045         * but we experienced synchronization issue resulting in stack traces like:
046         * java.lang.LinkageError: duplicate class definition:
047         * 
048         * <pre>
049         *    wicket/examples/repeater/RepeatingPage at java.lang.ClassLoader.defineClass1(Native Method)
050         * </pre>
051         * 
052         * This problem has gone since we synchronize the access.
053         */
054        private final ConcurrentMap<String, WeakReference<Class<?>>> classes = new ConcurrentHashMap<>();
055
056        @Override
057        public final Class<?> resolveClass(final String className) throws ClassNotFoundException
058        {
059                Class<?> clazz = null;
060                WeakReference<Class<?>> ref = classes.get(className);
061
062                // Might be garbage-collected between getting the WeakRef and retrieving
063                // the Class from it.
064                if (ref != null)
065                {
066                        clazz = ref.get();
067                }
068                if (clazz == null)
069                {
070                        switch (className)
071                        {
072                                case "byte":
073                                        clazz = byte.class;
074                                        break;
075                                case "short":
076                                        clazz = short.class;
077                                        break;
078                                case "int":
079                                        clazz = int.class;
080                                        break;
081                                case "long":
082                                        clazz = long.class;
083                                        break;
084                                case "float":
085                                        clazz = float.class;
086                                        break;
087                                case "double":
088                                        clazz = double.class;
089                                        break;
090                                case "boolean":
091                                        clazz = boolean.class;
092                                        break;
093                                case "char":
094                                        clazz = char.class;
095                                        break;
096                                default:
097                                        // synchronize on the only class member to load only one class at a time and
098                                        // prevent LinkageError. See above for more info
099                                        synchronized (classes)
100                                        {
101                                                clazz = Class.forName(className, false, getClassLoader());
102                                                if (clazz == null)
103                                                {
104                                                        throw new ClassNotFoundException(className);
105                                                }
106                                        }
107                                        classes.put(className, new WeakReference<Class<?>>(clazz));
108                                        break;
109                        }
110                }
111                return clazz;
112        }
113
114        @Override
115        public Iterator<URL> getResources(final String name)
116        {
117                Set<URL> resultSet = new TreeSet<>(new UrlExternalFormComparator());
118
119                try
120                {
121                        // Try the classloader for the wicket jar/bundle
122                        Enumeration<URL> resources = Application.class.getClassLoader().getResources(name);
123                        loadResources(resources, resultSet);
124
125                        // Try the classloader for the user's application jar/bundle
126                        resources = Application.get().getClass().getClassLoader().getResources(name);
127                        loadResources(resources, resultSet);
128
129                        // Try the context class loader
130                        resources = getClassLoader().getResources(name);
131                        loadResources(resources, resultSet);
132                }
133                catch (Exception e)
134                {
135                        throw new WicketRuntimeException(e);
136                }
137
138                return resultSet.iterator();
139        }
140
141        /**
142         * 
143         * @param resources
144         * @param loadedResources
145         */
146        private void loadResources(Enumeration<URL> resources, Set<URL> loadedResources)
147        {
148                if (resources != null)
149                {
150                        while (resources.hasMoreElements())
151                        {
152                                final URL url = resources.nextElement();
153                                loadedResources.add(url);
154                        }
155                }
156        }
157}