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