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