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.camel.util;
018    
019    import java.io.BufferedInputStream;
020    import java.io.IOException;
021    import java.io.InputStream;
022    import java.util.ArrayList;
023    import java.util.Collections;
024    import java.util.List;
025    import java.util.Properties;
026    import java.util.concurrent.ConcurrentHashMap;
027    
028    import org.apache.camel.NoFactoryAvailableException;
029    import org.apache.camel.spi.Injector;
030    
031    /**
032     * Finder to find factories from the resource classpath, usually <b>META-INF/services/org/apache/camel/</b>.
033     */
034    public class FactoryFinder {
035        // TODO: Extract interface to SPI
036    
037        protected final ConcurrentHashMap<String, Class> classMap = new ConcurrentHashMap<String, Class>();
038        private final String path;    
039    
040        public FactoryFinder() {
041            this("META-INF/services/org/apache/camel/");
042        }
043    
044        public FactoryFinder(String path) {
045            this.path = path;
046        }
047    
048        /**
049         * Creates a new instance of the given key
050         *
051         * @param key is the key to add to the path to find a text file containing
052         *            the factory name
053         * @return a newly created instance
054         */
055        public Object newInstance(String key) throws IllegalAccessException, InstantiationException, IOException,
056            ClassNotFoundException {
057            return newInstance(key, (String)null);
058        }
059    
060        public Object newInstance(String key, String propertyPrefix) throws IllegalAccessException,
061            InstantiationException, IOException, ClassNotFoundException {
062            Class clazz = findClass(key, propertyPrefix);
063            return clazz.newInstance();
064        }
065    
066        public Object newInstance(String key, Injector injector) throws IOException, ClassNotFoundException {
067            return newInstance(key, injector, (String)null);
068        }
069    
070        public Object newInstance(String key, Injector injector, String propertyPrefix) throws IOException,
071            ClassNotFoundException {
072            Class<?> type = findClass(key, propertyPrefix);
073            return injector.newInstance(type);
074        }
075    
076        public <T> T newInstance(String key, Injector injector, Class<T> expectedType) throws IOException,
077            ClassNotFoundException {
078            return newInstance(key, injector, null, expectedType);
079        }
080    
081        public <T> T newInstance(String key, Injector injector, String propertyPrefix, Class<T> expectedType)
082            throws IOException, ClassNotFoundException {
083            Class<?> type = findClass(key, propertyPrefix);
084            Object value = injector.newInstance(type);
085            if (expectedType.isInstance(value)) {
086                return expectedType.cast(value);
087            } else {
088                throw new ClassCastException("Not instanceof " + expectedType.getName() + " value: " + value);
089            }
090        }
091    
092        public <T> List<T> newInstances(String key, Injector injector, Class<T> type) throws IOException,
093            ClassNotFoundException {
094            List<Class> list = findClasses(key);
095            List<T> answer = new ArrayList<T>(list.size());
096            answer.add(newInstance(key, injector, type));
097            return answer;
098        }
099    
100        public Class findClass(String key) throws ClassNotFoundException, IOException {
101            return findClass(key, null);
102        }
103    
104        public Class findClass(String key, String propertyPrefix) throws ClassNotFoundException, IOException {
105            if (propertyPrefix == null) {
106                propertyPrefix = "";
107            }
108    
109            Class clazz = classMap.get(propertyPrefix + key);
110            if (clazz == null) {
111                clazz = newInstance(doFindFactoryProperties(key), propertyPrefix);
112                if (clazz != null) {
113                    classMap.put(propertyPrefix + key, clazz);
114                }
115            }
116            return clazz;
117        }
118    
119        public List<Class> findClasses(String key) throws ClassNotFoundException, IOException {
120            return findClasses(key, null);
121        }
122    
123        public List<Class> findClasses(String key, String propertyPrefix) throws ClassNotFoundException, IOException {
124            // TODO change to support finding multiple classes on the classpath!
125            Class type = findClass(key, propertyPrefix);
126            return Collections.singletonList(type);
127        }
128    
129        public String getPath() {
130            return path;
131        }
132    
133        private Class newInstance(Properties properties, String propertyPrefix) throws ClassNotFoundException, IOException {
134            String className = properties.getProperty(propertyPrefix + "class");
135            if (className == null) {
136                throw new IOException("Expected property is missing: " + propertyPrefix + "class");
137            }
138    
139            Class clazz = ObjectHelper.loadClass(className);
140            if (clazz == null) {
141                throw new ClassNotFoundException(className);
142            }
143            return clazz;
144        }
145    
146        private Properties doFindFactoryProperties(String key) throws IOException {
147            String uri = path + key;
148    
149            InputStream in = ObjectHelper.loadResourceAsStream(uri);
150            if (in == null) {
151                throw new NoFactoryAvailableException(uri);
152            }
153    
154            // lets load the file
155            BufferedInputStream reader = null;
156            try {
157                reader = new BufferedInputStream(in);
158                Properties properties = new Properties();
159                properties.load(reader);
160                return properties;
161            } finally {
162                ObjectHelper.close(reader, key, null);
163                ObjectHelper.close(in, key, null);
164            }
165        }
166        
167    }