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     */
017    package org.apache.camel.util;
018    
019    import java.io.File;
020    import java.io.FileInputStream;
021    import java.io.FileNotFoundException;
022    import java.io.IOException;
023    import java.io.InputStream;
024    import java.net.HttpURLConnection;
025    import java.net.MalformedURLException;
026    import java.net.URI;
027    import java.net.URISyntaxException;
028    import java.net.URL;
029    import java.net.URLConnection;
030    import java.net.URLDecoder;
031    import java.util.Map;
032    
033    import org.apache.camel.spi.ClassResolver;
034    import org.slf4j.Logger;
035    import org.slf4j.LoggerFactory;
036    
037    /**
038     * Helper class for loading resources on the classpath or file system.
039     */
040    public 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    }