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.util; 018 019import java.io.ByteArrayInputStream; 020import java.io.File; 021import java.io.FileInputStream; 022import java.io.FileNotFoundException; 023import java.io.IOException; 024import java.io.InputStream; 025import java.net.HttpURLConnection; 026import java.net.MalformedURLException; 027import java.net.URI; 028import java.net.URISyntaxException; 029import java.net.URL; 030import java.net.URLConnection; 031import java.net.URLDecoder; 032import java.util.Map; 033 034import org.apache.camel.CamelContext; 035import org.apache.camel.Exchange; 036import org.apache.camel.RuntimeCamelException; 037import org.apache.camel.impl.DefaultExchange; 038import org.apache.camel.language.simple.SimpleLanguage; 039import org.apache.camel.spi.ClassResolver; 040import org.slf4j.Logger; 041import org.slf4j.LoggerFactory; 042 043/** 044 * Helper class for loading resources on the classpath or file system. 045 */ 046public final class ResourceHelper { 047 048 private static final Logger LOG = LoggerFactory.getLogger(ResourceHelper.class); 049 050 private ResourceHelper() { 051 // utility class 052 } 053 054 /** 055 * Resolves the expression/predicate whether it refers to an external script on the file/classpath etc. 056 * This requires to use the prefix <tt>resource:</tt> such as <tt>resource:classpath:com/foo/myscript.groovy</tt>, 057 * <tt>resource:file:/var/myscript.groovy</tt>. 058 * <p/> 059 * If not then the returned value is returned as-is. 060 */ 061 public static String resolveOptionalExternalScript(CamelContext camelContext, String expression) { 062 return resolveOptionalExternalScript(camelContext, null, expression); 063 } 064 065 /** 066 * Resolves the expression/predicate whether it refers to an external script on the file/classpath etc. 067 * This requires to use the prefix <tt>resource:</tt> such as <tt>resource:classpath:com/foo/myscript.groovy</tt>, 068 * <tt>resource:file:/var/myscript.groovy</tt>. 069 * <p/> 070 * If not then the returned value is returned as-is. 071 * <p/> 072 * If the exchange is provided (not null), then the external script can be referred via simple language for dynamic values, etc. 073 * <tt>resource:classpath:${header.myFileName}</tt> 074 */ 075 public static String resolveOptionalExternalScript(CamelContext camelContext, Exchange exchange, String expression) { 076 if (expression == null) { 077 return null; 078 } 079 String external = expression; 080 081 // must be one line only 082 int newLines = StringHelper.countChar(expression, '\n'); 083 if (newLines > 1) { 084 // okay then just use as-is 085 return expression; 086 } 087 088 // must start with resource: to denote an external resource 089 if (external.startsWith("resource:")) { 090 external = external.substring(9); 091 092 if (hasScheme(external)) { 093 094 if (exchange != null && SimpleLanguage.hasSimpleFunction(external)) { 095 SimpleLanguage simple = (SimpleLanguage) exchange.getContext().resolveLanguage("simple"); 096 external = simple.createExpression(external).evaluate(exchange, String.class); 097 } 098 099 InputStream is = null; 100 try { 101 is = resolveMandatoryResourceAsInputStream(camelContext, external); 102 expression = camelContext.getTypeConverter().convertTo(String.class, is); 103 } catch (IOException e) { 104 throw new RuntimeCamelException("Cannot load resource " + external, e); 105 } finally { 106 IOHelper.close(is); 107 } 108 } 109 } 110 111 return expression; 112 } 113 114 /** 115 * Determines whether the URI has a scheme (e.g. file:, classpath: or http:) 116 * 117 * @param uri the URI 118 * @return <tt>true</tt> if the URI starts with a scheme 119 */ 120 public static boolean hasScheme(String uri) { 121 if (uri == null) { 122 return false; 123 } 124 125 return uri.startsWith("file:") || uri.startsWith("classpath:") || uri.startsWith("http:"); 126 } 127 128 /** 129 * Gets the scheme from the URI (e.g. file:, classpath: or http:) 130 * 131 * @param uri the uri 132 * @return the scheme, or <tt>null</tt> if no scheme 133 */ 134 public static String getScheme(String uri) { 135 if (hasScheme(uri)) { 136 return uri.substring(0, uri.indexOf(":") + 1); 137 } else { 138 return null; 139 } 140 } 141 142 /** 143 * Resolves the mandatory resource. 144 * <p/> 145 * The resource uri can refer to the following systems to be loaded from 146 * <ul> 147 * <il>file:nameOfFile - to refer to the file system</il> 148 * <il>classpath:nameOfFile - to refer to the classpath (default)</il> 149 * <il>http:uri - to load the resource using HTTP</il> 150 * <il>ref:nameOfBean - to lookup the resource in the {@link org.apache.camel.spi.Registry}</il> 151 * <il>bean:nameOfBean.methodName or bean:nameOfBean::methodName - to lookup a bean in the {@link org.apache.camel.spi.Registry} and call the method</il> 152 * <il><customProtocol>:uri - to lookup the resource using a custom {@link java.net.URLStreamHandler} registered for the <customProtocol>, 153 * on how to register it @see java.net.URL#URL(java.lang.String, java.lang.String, int, java.lang.String)</il> 154 * </ul> 155 * If no prefix has been given, then the resource is loaded from the classpath 156 * <p/> 157 * If possible recommended to use {@link #resolveMandatoryResourceAsUrl(org.apache.camel.spi.ClassResolver, String)} 158 * 159 * @param camelContext the Camel Context 160 * @param uri URI of the resource 161 * @return the resource as an {@link InputStream}. Remember to close this stream after usage. 162 * @throws java.io.IOException is thrown if the resource file could not be found or loaded as {@link InputStream} 163 */ 164 public static InputStream resolveMandatoryResourceAsInputStream(CamelContext camelContext, String uri) throws IOException { 165 if (uri.startsWith("ref:")) { 166 String ref = uri.substring(4); 167 String value = CamelContextHelper.mandatoryLookup(camelContext, ref, String.class); 168 return new ByteArrayInputStream(value.getBytes()); 169 } else if (uri.startsWith("bean:")) { 170 String bean = uri.substring(5); 171 Exchange dummy = new DefaultExchange(camelContext); 172 Object out = camelContext.resolveLanguage("bean").createExpression(bean).evaluate(dummy, Object.class); 173 if (dummy.getException() != null) { 174 IOException io = new IOException("Cannot find resource: " + uri + " from calling the bean"); 175 io.initCause(dummy.getException()); 176 throw io; 177 } 178 if (out != null) { 179 InputStream is = camelContext.getTypeConverter().tryConvertTo(InputStream.class, dummy, out); 180 if (is == null) { 181 String text = camelContext.getTypeConverter().tryConvertTo(String.class, dummy, out); 182 if (text != null) { 183 return new ByteArrayInputStream(text.getBytes()); 184 } 185 } else { 186 return is; 187 } 188 } else { 189 throw new IOException("Cannot find resource: " + uri + " from calling the bean"); 190 } 191 } 192 193 InputStream is = resolveResourceAsInputStream(camelContext.getClassResolver(), uri); 194 if (is == null) { 195 String resolvedName = resolveUriPath(uri); 196 throw new FileNotFoundException("Cannot find resource: " + resolvedName + " in classpath for URI: " + uri); 197 } else { 198 return is; 199 } 200 } 201 202 /** 203 * Resolves the mandatory resource. 204 * <p/> 205 * If possible recommended to use {@link #resolveMandatoryResourceAsUrl(org.apache.camel.spi.ClassResolver, String)} 206 * 207 * @param classResolver the class resolver to load the resource from the classpath 208 * @param uri URI of the resource 209 * @return the resource as an {@link InputStream}. Remember to close this stream after usage. 210 * @throws java.io.IOException is thrown if the resource file could not be found or loaded as {@link InputStream} 211 * @deprecated use {@link #resolveMandatoryResourceAsInputStream(CamelContext, String)} 212 */ 213 @Deprecated 214 public static InputStream resolveMandatoryResourceAsInputStream(ClassResolver classResolver, String uri) throws IOException { 215 InputStream is = resolveResourceAsInputStream(classResolver, uri); 216 if (is == null) { 217 String resolvedName = resolveUriPath(uri); 218 throw new FileNotFoundException("Cannot find resource: " + resolvedName + " in classpath for URI: " + uri); 219 } else { 220 return is; 221 } 222 } 223 224 /** 225 * Resolves the resource. 226 * <p/> 227 * If possible recommended to use {@link #resolveMandatoryResourceAsUrl(org.apache.camel.spi.ClassResolver, String)} 228 * 229 * @param classResolver the class resolver to load the resource from the classpath 230 * @param uri URI of the resource 231 * @return the resource as an {@link InputStream}. Remember to close this stream after usage. Or <tt>null</tt> if not found. 232 * @throws java.io.IOException is thrown if error loading the resource 233 */ 234 public static InputStream resolveResourceAsInputStream(ClassResolver classResolver, String uri) throws IOException { 235 if (uri.startsWith("file:")) { 236 uri = StringHelper.after(uri, "file:"); 237 uri = tryDecodeUri(uri); 238 LOG.trace("Loading resource: {} from file system", uri); 239 return new FileInputStream(uri); 240 } else if (uri.startsWith("http:")) { 241 URL url = new URL(uri); 242 LOG.trace("Loading resource: {} from HTTP", uri); 243 URLConnection con = url.openConnection(); 244 con.setUseCaches(false); 245 try { 246 return con.getInputStream(); 247 } catch (IOException e) { 248 // close the http connection to avoid 249 // leaking gaps in case of an exception 250 if (con instanceof HttpURLConnection) { 251 ((HttpURLConnection) con).disconnect(); 252 } 253 throw e; 254 } 255 } else if (uri.startsWith("classpath:")) { 256 uri = StringHelper.after(uri, "classpath:"); 257 uri = tryDecodeUri(uri); 258 } else if (uri.contains(":")) { 259 LOG.trace("Loading resource: {} with UrlHandler for protocol {}", uri, uri.split(":")[0]); 260 URL url = new URL(uri); 261 URLConnection con = url.openConnection(); 262 return con.getInputStream(); 263 } 264 265 // load from classpath by default 266 String resolvedName = resolveUriPath(uri); 267 LOG.trace("Loading resource: {} from classpath", resolvedName); 268 return classResolver.loadResourceAsStream(resolvedName); 269 } 270 271 /** 272 * Resolves the mandatory resource. 273 * 274 * @param classResolver the class resolver to load the resource from the classpath 275 * @param uri uri of the resource 276 * @return the resource as an {@link java.net.URL}. 277 * @throws java.io.FileNotFoundException is thrown if the resource file could not be found 278 * @throws java.net.MalformedURLException if the URI is malformed 279 */ 280 public static URL resolveMandatoryResourceAsUrl(ClassResolver classResolver, String uri) throws FileNotFoundException, MalformedURLException { 281 URL url = resolveResourceAsUrl(classResolver, uri); 282 if (url == null) { 283 String resolvedName = resolveUriPath(uri); 284 throw new FileNotFoundException("Cannot find resource: " + resolvedName + " in classpath for URI: " + uri); 285 } else { 286 return url; 287 } 288 } 289 290 /** 291 * Resolves the resource. 292 * 293 * @param classResolver the class resolver to load the resource from the classpath 294 * @param uri uri of the resource 295 * @return the resource as an {@link java.net.URL}. Or <tt>null</tt> if not found. 296 * @throws java.net.MalformedURLException if the URI is malformed 297 */ 298 public static URL resolveResourceAsUrl(ClassResolver classResolver, String uri) throws MalformedURLException { 299 if (uri.startsWith("file:")) { 300 // check if file exists first 301 String name = StringHelper.after(uri, "file:"); 302 uri = tryDecodeUri(uri); 303 LOG.trace("Loading resource: {} from file system", uri); 304 File file = new File(name); 305 if (!file.exists()) { 306 return null; 307 } 308 return new URL(uri); 309 } else if (uri.startsWith("http:")) { 310 LOG.trace("Loading resource: {} from HTTP", uri); 311 return new URL(uri); 312 } else if (uri.startsWith("classpath:")) { 313 uri = StringHelper.after(uri, "classpath:"); 314 uri = tryDecodeUri(uri); 315 } else if (uri.contains(":")) { 316 LOG.trace("Loading resource: {} with UrlHandler for protocol {}", uri, uri.split(":")[0]); 317 return new URL(uri); 318 } 319 320 // load from classpath by default 321 String resolvedName = resolveUriPath(uri); 322 LOG.trace("Loading resource: {} from classpath", resolvedName); 323 return classResolver.loadResourceAsURL(resolvedName); 324 } 325 326 /** 327 * Is the given uri a http uri? 328 * 329 * @param uri the uri 330 * @return <tt>true</tt> if the uri starts with <tt>http:</tt> or <tt>https:</tt> 331 */ 332 public static boolean isHttpUri(String uri) { 333 if (uri == null) { 334 return false; 335 } 336 return uri.startsWith("http:") || uri.startsWith("https:"); 337 } 338 339 /** 340 * Appends the parameters to the given uri 341 * 342 * @param uri the uri 343 * @param parameters the additional parameters (will clear the map) 344 * @return a new uri with the additional parameters appended 345 * @throws URISyntaxException is thrown if the uri is invalid 346 */ 347 public static String appendParameters(String uri, Map<String, Object> parameters) throws URISyntaxException { 348 // add additional parameters to the resource uri 349 if (!parameters.isEmpty()) { 350 String query = URISupport.createQueryString(parameters); 351 URI u = new URI(uri); 352 u = URISupport.createURIWithQuery(u, query); 353 parameters.clear(); 354 return u.toString(); 355 } else { 356 return uri; 357 } 358 } 359 360 /** 361 * Helper operation used to remove relative path notation from 362 * resources. Most critical for resources on the Classpath 363 * as resource loaders will not resolve the relative paths correctly. 364 * 365 * @param name the name of the resource to load 366 * @return the modified or unmodified string if there were no changes 367 */ 368 private static String resolveUriPath(String name) { 369 // compact the path and use / as separator as that's used for loading resources on the classpath 370 return FileUtil.compactPath(name, '/'); 371 } 372 373 /** 374 * Tries decoding the uri. 375 * 376 * @param uri the uri 377 * @return the decoded uri, or the original uri 378 */ 379 private static String tryDecodeUri(String uri) { 380 try { 381 // try to decode as the uri may contain %20 for spaces etc 382 uri = URLDecoder.decode(uri, "UTF-8"); 383 } catch (Exception e) { 384 LOG.trace("Error URL decoding uri using UTF-8 encoding: {}. This exception is ignored.", uri); 385 // ignore 386 } 387 return uri; 388 } 389 390}