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