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