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