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 or 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 Exchange dummy = new DefaultExchange(camelContext); 151 Object out = camelContext.resolveLanguage("bean").createExpression(bean).evaluate(dummy, Object.class); 152 if (dummy.getException() != null) { 153 IOException io = new IOException("Cannot find resource: " + uri + " from calling the bean"); 154 io.initCause(dummy.getException()); 155 throw io; 156 } 157 if (out != null) { 158 InputStream is = camelContext.getTypeConverter().tryConvertTo(InputStream.class, dummy, out); 159 if (is == null) { 160 String text = camelContext.getTypeConverter().tryConvertTo(String.class, dummy, out); 161 if (text != null) { 162 return new ByteArrayInputStream(text.getBytes()); 163 } 164 } else { 165 return is; 166 } 167 } else { 168 throw new IOException("Cannot find resource: " + uri + " from calling the bean"); 169 } 170 } 171 172 InputStream is = resolveResourceAsInputStream(camelContext.getClassResolver(), uri); 173 if (is == null) { 174 String resolvedName = resolveUriPath(uri); 175 throw new FileNotFoundException("Cannot find resource: " + resolvedName + " in classpath for URI: " + uri); 176 } else { 177 return is; 178 } 179 } 180 181 /** 182 * Resolves the mandatory resource. 183 * <p/> 184 * If possible recommended to use {@link #resolveMandatoryResourceAsUrl(org.apache.camel.spi.ClassResolver, String)} 185 * 186 * @param classResolver the class resolver to load the resource from the classpath 187 * @param uri URI of the resource 188 * @return the resource as an {@link InputStream}. Remember to close this stream after usage. 189 * @throws java.io.IOException is thrown if the resource file could not be found or loaded as {@link InputStream} 190 * @deprecated use {@link #resolveMandatoryResourceAsInputStream(CamelContext, String)} 191 */ 192 @Deprecated 193 public static InputStream resolveMandatoryResourceAsInputStream(ClassResolver classResolver, String uri) throws IOException { 194 InputStream is = resolveResourceAsInputStream(classResolver, uri); 195 if (is == null) { 196 String resolvedName = resolveUriPath(uri); 197 throw new FileNotFoundException("Cannot find resource: " + resolvedName + " in classpath for URI: " + uri); 198 } else { 199 return is; 200 } 201 } 202 203 /** 204 * Resolves the resource. 205 * <p/> 206 * If possible recommended to use {@link #resolveMandatoryResourceAsUrl(org.apache.camel.spi.ClassResolver, String)} 207 * 208 * @param classResolver the class resolver to load the resource from the classpath 209 * @param uri URI of the resource 210 * @return the resource as an {@link InputStream}. Remember to close this stream after usage. Or <tt>null</tt> if not found. 211 * @throws java.io.IOException is thrown if error loading the resource 212 */ 213 public static InputStream resolveResourceAsInputStream(ClassResolver classResolver, String uri) throws IOException { 214 if (uri.startsWith("file:")) { 215 uri = ObjectHelper.after(uri, "file:"); 216 uri = tryDecodeUri(uri); 217 LOG.trace("Loading resource: {} from file system", uri); 218 return new FileInputStream(uri); 219 } else if (uri.startsWith("http:")) { 220 URL url = new URL(uri); 221 LOG.trace("Loading resource: {} from HTTP", uri); 222 URLConnection con = url.openConnection(); 223 con.setUseCaches(false); 224 try { 225 return con.getInputStream(); 226 } catch (IOException e) { 227 // close the http connection to avoid 228 // leaking gaps in case of an exception 229 if (con instanceof HttpURLConnection) { 230 ((HttpURLConnection) con).disconnect(); 231 } 232 throw e; 233 } 234 } else if (uri.startsWith("classpath:")) { 235 uri = ObjectHelper.after(uri, "classpath:"); 236 uri = tryDecodeUri(uri); 237 } else if (uri.contains(":")) { 238 LOG.trace("Loading resource: {} with UrlHandler for protocol {}", uri, uri.split(":")[0]); 239 URL url = new URL(uri); 240 URLConnection con = url.openConnection(); 241 return con.getInputStream(); 242 } 243 244 // load from classpath by default 245 String resolvedName = resolveUriPath(uri); 246 LOG.trace("Loading resource: {} from classpath", resolvedName); 247 return classResolver.loadResourceAsStream(resolvedName); 248 } 249 250 /** 251 * Resolves the mandatory resource. 252 * 253 * @param classResolver the class resolver to load the resource from the classpath 254 * @param uri uri of the resource 255 * @return the resource as an {@link java.net.URL}. 256 * @throws java.io.FileNotFoundException is thrown if the resource file could not be found 257 * @throws java.net.MalformedURLException if the URI is malformed 258 */ 259 public static URL resolveMandatoryResourceAsUrl(ClassResolver classResolver, String uri) throws FileNotFoundException, MalformedURLException { 260 URL url = resolveResourceAsUrl(classResolver, uri); 261 if (url == null) { 262 String resolvedName = resolveUriPath(uri); 263 throw new FileNotFoundException("Cannot find resource: " + resolvedName + " in classpath for URI: " + uri); 264 } else { 265 return url; 266 } 267 } 268 269 /** 270 * Resolves the resource. 271 * 272 * @param classResolver the class resolver to load the resource from the classpath 273 * @param uri uri of the resource 274 * @return the resource as an {@link java.net.URL}. Or <tt>null</tt> if not found. 275 * @throws java.net.MalformedURLException if the URI is malformed 276 */ 277 public static URL resolveResourceAsUrl(ClassResolver classResolver, String uri) throws MalformedURLException { 278 if (uri.startsWith("file:")) { 279 // check if file exists first 280 String name = ObjectHelper.after(uri, "file:"); 281 uri = tryDecodeUri(uri); 282 LOG.trace("Loading resource: {} from file system", uri); 283 File file = new File(name); 284 if (!file.exists()) { 285 return null; 286 } 287 return new URL(uri); 288 } else if (uri.startsWith("http:")) { 289 LOG.trace("Loading resource: {} from HTTP", uri); 290 return new URL(uri); 291 } else if (uri.startsWith("classpath:")) { 292 uri = ObjectHelper.after(uri, "classpath:"); 293 uri = tryDecodeUri(uri); 294 } else if (uri.contains(":")) { 295 LOG.trace("Loading resource: {} with UrlHandler for protocol {}", uri, uri.split(":")[0]); 296 return new URL(uri); 297 } 298 299 // load from classpath by default 300 String resolvedName = resolveUriPath(uri); 301 LOG.trace("Loading resource: {} from classpath", resolvedName); 302 return classResolver.loadResourceAsURL(resolvedName); 303 } 304 305 /** 306 * Is the given uri a http uri? 307 * 308 * @param uri the uri 309 * @return <tt>true</tt> if the uri starts with <tt>http:</tt> or <tt>https:</tt> 310 */ 311 public static boolean isHttpUri(String uri) { 312 if (uri == null) { 313 return false; 314 } 315 return uri.startsWith("http:") || uri.startsWith("https:"); 316 } 317 318 /** 319 * Appends the parameters to the given uri 320 * 321 * @param uri the uri 322 * @param parameters the additional parameters (will clear the map) 323 * @return a new uri with the additional parameters appended 324 * @throws URISyntaxException is thrown if the uri is invalid 325 */ 326 public static String appendParameters(String uri, Map<String, Object> parameters) throws URISyntaxException { 327 // add additional parameters to the resource uri 328 if (!parameters.isEmpty()) { 329 String query = URISupport.createQueryString(parameters); 330 URI u = new URI(uri); 331 u = URISupport.createURIWithQuery(u, query); 332 parameters.clear(); 333 return u.toString(); 334 } else { 335 return uri; 336 } 337 } 338 339 /** 340 * Helper operation used to remove relative path notation from 341 * resources. Most critical for resources on the Classpath 342 * as resource loaders will not resolve the relative paths correctly. 343 * 344 * @param name the name of the resource to load 345 * @return the modified or unmodified string if there were no changes 346 */ 347 private static String resolveUriPath(String name) { 348 // compact the path and use / as separator as that's used for loading resources on the classpath 349 return FileUtil.compactPath(name, '/'); 350 } 351 352 /** 353 * Tries decoding the uri. 354 * 355 * @param uri the uri 356 * @return the decoded uri, or the original uri 357 */ 358 private static String tryDecodeUri(String uri) { 359 try { 360 // try to decode as the uri may contain %20 for spaces etc 361 uri = URLDecoder.decode(uri, "UTF-8"); 362 } catch (Exception e) { 363 LOG.trace("Error URL decoding uri using UTF-8 encoding: {}. This exception is ignored.", uri); 364 // ignore 365 } 366 return uri; 367 } 368 369}