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.camel.impl; 018 019import java.io.BufferedInputStream; 020import java.io.IOException; 021import java.io.InputStream; 022import java.util.ArrayList; 023import java.util.Collections; 024import java.util.List; 025import java.util.Properties; 026import java.util.concurrent.ConcurrentHashMap; 027import java.util.concurrent.ConcurrentMap; 028 029import org.apache.camel.NoFactoryAvailableException; 030import org.apache.camel.spi.ClassResolver; 031import org.apache.camel.spi.FactoryFinder; 032import org.apache.camel.spi.Injector; 033import org.apache.camel.util.CastUtils; 034import org.apache.camel.util.IOHelper; 035 036/** 037 * Default factory finder. 038 */ 039public class DefaultFactoryFinder implements FactoryFinder { 040 041 private final ConcurrentMap<String, Class<?>> classMap = new ConcurrentHashMap<>(); 042 private final ConcurrentMap<String, Boolean> classesNotFound = new ConcurrentHashMap<>(); 043 private final ConcurrentMap<String, Exception> classesNotFoundExceptions = new ConcurrentHashMap<>(); 044 private final ClassResolver classResolver; 045 private final String path; 046 047 public DefaultFactoryFinder(ClassResolver classResolver, String resourcePath) { 048 this.classResolver = classResolver; 049 this.path = resourcePath; 050 } 051 052 @Override 053 public String getResourcePath() { 054 return path; 055 } 056 057 @Override 058 public Object newInstance(String key) throws NoFactoryAvailableException { 059 try { 060 return newInstance(key, null); 061 } catch (Exception e) { 062 throw new NoFactoryAvailableException(key, e); 063 } 064 } 065 066 @Override 067 public <T> List<T> newInstances(String key, Injector injector, Class<T> type) throws ClassNotFoundException, IOException { 068 List<Class<T>> list = CastUtils.cast(findClasses(key)); 069 List<T> answer = new ArrayList<T>(list.size()); 070 answer.add(newInstance(key, injector, type)); 071 return answer; 072 } 073 074 @Override 075 public Class<?> findClass(String key) throws ClassNotFoundException, IOException { 076 return findClass(key, null); 077 } 078 079 @Override 080 public Class<?> findClass(String key, String propertyPrefix) throws ClassNotFoundException, IOException { 081 final String prefix = propertyPrefix != null ? propertyPrefix : ""; 082 final String classKey = prefix + key; 083 084 return addToClassMap(classKey, () -> newInstance(doFindFactoryProperties(key), prefix)); 085 } 086 087 @Override 088 public Class<?> findClass(String key, String propertyPrefix, Class<?> clazz) throws ClassNotFoundException, IOException { 089 // Just ignore clazz which is only useful for OSGiFactoryFinder 090 return findClass(key, propertyPrefix); 091 } 092 093 private Object newInstance(String key, String propertyPrefix) throws IllegalAccessException, 094 InstantiationException, IOException, ClassNotFoundException { 095 Class<?> clazz = findClass(key, propertyPrefix); 096 return clazz.newInstance(); 097 } 098 099 private <T> T newInstance(String key, Injector injector, Class<T> expectedType) throws IOException, 100 ClassNotFoundException { 101 return newInstance(key, injector, null, expectedType); 102 } 103 104 private <T> T newInstance(String key, Injector injector, String propertyPrefix, Class<T> expectedType) 105 throws IOException, ClassNotFoundException { 106 Class<?> type = findClass(key, propertyPrefix); 107 Object value = injector.newInstance(type); 108 if (expectedType.isInstance(value)) { 109 return expectedType.cast(value); 110 } else { 111 throw new ClassCastException("Not instanceof " + expectedType.getName() + " value: " + value); 112 } 113 } 114 115 private List<Class<?>> findClasses(String key) throws ClassNotFoundException, IOException { 116 return findClasses(key, null); 117 } 118 119 private List<Class<?>> findClasses(String key, String propertyPrefix) throws ClassNotFoundException, IOException { 120 Class<?> type = findClass(key, propertyPrefix); 121 return Collections.<Class<?>>singletonList(type); 122 } 123 124 private Class<?> newInstance(Properties properties, String propertyPrefix) throws ClassNotFoundException, IOException { 125 String className = properties.getProperty(propertyPrefix + "class"); 126 if (className == null) { 127 throw new IOException("Expected property is missing: " + propertyPrefix + "class"); 128 } 129 130 Class<?> clazz = classResolver.resolveClass(className); 131 if (clazz == null) { 132 throw new ClassNotFoundException(className); 133 } 134 return clazz; 135 } 136 137 private Properties doFindFactoryProperties(String key) throws IOException { 138 String uri = path + key; 139 140 InputStream in = classResolver.loadResourceAsStream(uri); 141 if (in == null) { 142 throw new NoFactoryAvailableException(uri); 143 } 144 145 // lets load the file 146 BufferedInputStream reader = null; 147 try { 148 reader = IOHelper.buffered(in); 149 Properties properties = new Properties(); 150 properties.load(reader); 151 return properties; 152 } finally { 153 IOHelper.close(reader, key, null); 154 IOHelper.close(in, key, null); 155 } 156 } 157 158 /* 159 * This is a wrapper function to deal with exceptions in lambdas: the exception 160 * is wrapped by a runtime exception (WrappedRuntimeException) which we catch 161 * later on with the only purpose to re-throw the original exception. 162 */ 163 protected Class<?> addToClassMap(String key, ClassSupplier mappingFunction) throws ClassNotFoundException, IOException { 164 try { 165 if (classesNotFoundExceptions.containsKey(key) || classesNotFound.containsKey(key)) { 166 Exception e = classesNotFoundExceptions.get(key); 167 if (e == null) { 168 return null; 169 } else { 170 throw new WrappedRuntimeException(e); 171 } 172 } 173 174 Class<?> suppliedClass = classMap.computeIfAbsent(key, (String classKey) -> { 175 try { 176 return mappingFunction.get(); 177 } catch (ClassNotFoundException e) { 178 classesNotFoundExceptions.put(key, e); 179 throw new WrappedRuntimeException(e); 180 } catch (NoFactoryAvailableException e) { 181 classesNotFoundExceptions.put(key, e); 182 throw new WrappedRuntimeException(e); 183 } catch (IOException e) { 184 throw new WrappedRuntimeException(e); 185 } 186 }); 187 188 if (suppliedClass == null) { 189 // mark the key as non-resolvable to prevent pointless searching 190 classesNotFound.put(key, Boolean.TRUE); 191 } 192 193 return suppliedClass; 194 } catch (WrappedRuntimeException e) { 195 if (e.getCause() instanceof ClassNotFoundException) { 196 throw (ClassNotFoundException)e.getCause(); 197 } else if (e.getCause() instanceof NoFactoryAvailableException) { 198 throw (NoFactoryAvailableException)e.getCause(); 199 } else if (e.getCause() instanceof IOException) { 200 throw (IOException)e.getCause(); 201 } else { 202 throw new RuntimeException(e.getCause()); 203 } 204 } 205 } 206 207 @FunctionalInterface 208 protected interface ClassSupplier { 209 Class<?> get() throws ClassNotFoundException, IOException; 210 } 211 212 private final class WrappedRuntimeException extends RuntimeException { 213 WrappedRuntimeException(Exception e) { 214 super(e); 215 } 216 } 217}