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