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}