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.commons.configuration2.io;
018
019import java.io.File;
020import java.io.FileNotFoundException;
021import java.io.FileOutputStream;
022import java.io.IOException;
023import java.io.InputStream;
024import java.io.OutputStream;
025import java.net.HttpURLConnection;
026import java.net.MalformedURLException;
027import java.net.URL;
028import java.net.URLConnection;
029
030import org.apache.commons.configuration2.ex.ConfigurationException;
031
032/**
033 * FileSystem that uses java.io.File or HttpClient.
034 *
035 * @since 1.7
036 */
037public class DefaultFileSystem extends FileSystem {
038
039    /**
040     * Wraps the output stream so errors can be detected in the HTTP response.
041     *
042     * @since 1.7
043     */
044    private static final class HttpOutputStream extends VerifiableOutputStream {
045        /** The wrapped OutputStream */
046        private final OutputStream stream;
047
048        /** The HttpURLConnection */
049        private final HttpURLConnection connection;
050
051        public HttpOutputStream(final OutputStream stream, final HttpURLConnection connection) {
052            this.stream = stream;
053            this.connection = connection;
054        }
055
056        @Override
057        public void close() throws IOException {
058            stream.close();
059        }
060
061        @Override
062        public void flush() throws IOException {
063            stream.flush();
064        }
065
066        @Override
067        public String toString() {
068            return stream.toString();
069        }
070
071        @Override
072        public void verify() throws IOException {
073            if (connection.getResponseCode() >= HttpURLConnection.HTTP_BAD_REQUEST) {
074                throw new IOException("HTTP Error " + connection.getResponseCode() + " " + connection.getResponseMessage());
075            }
076        }
077
078        @Override
079        public void write(final byte[] bytes) throws IOException {
080            stream.write(bytes);
081        }
082
083        @Override
084        public void write(final byte[] bytes, final int i, final int i1) throws IOException {
085            stream.write(bytes, i, i1);
086        }
087
088        @Override
089        public void write(final int i) throws IOException {
090            stream.write(i);
091        }
092    }
093
094    /**
095     * Create the path to the specified file.
096     *
097     * @param file the target file
098     * @throws ConfigurationException if the path cannot be created
099     */
100    private void createPath(final File file) throws ConfigurationException {
101        // create the path to the file if the file doesn't exist
102        if (file != null && !file.exists()) {
103            final File parent = file.getParentFile();
104            if (parent != null && !parent.exists() && !parent.mkdirs()) {
105                throw new ConfigurationException("Cannot create path: " + parent);
106            }
107        }
108    }
109
110    @Override
111    public String getBasePath(final String path) {
112        final URL url;
113        try {
114            url = getURL(null, path);
115            return FileLocatorUtils.getBasePath(url);
116        } catch (final Exception e) {
117            return null;
118        }
119    }
120
121    @Override
122    public String getFileName(final String path) {
123        final URL url;
124        try {
125            url = getURL(null, path);
126            return FileLocatorUtils.getFileName(url);
127        } catch (final Exception e) {
128            return null;
129        }
130    }
131
132    @Override
133    public InputStream getInputStream(final URL url) throws ConfigurationException {
134        return getInputStream(url, null);
135    }
136
137    @Override
138    public InputStream getInputStream(final URL url, final URLConnectionOptions urlConnectionOptions) throws ConfigurationException {
139        // throw an exception if the target URL is a directory
140        final File file = FileLocatorUtils.fileFromURL(url);
141        if (file != null && file.isDirectory()) {
142            throw new ConfigurationException("Cannot load a configuration from a directory");
143        }
144
145        try {
146            return urlConnectionOptions == null ? url.openStream() : urlConnectionOptions.openConnection(url).getInputStream();
147        } catch (final Exception e) {
148            throw new ConfigurationException("Unable to load the configuration from the URL " + url, e);
149        }
150    }
151
152    @Override
153    public OutputStream getOutputStream(final File file) throws ConfigurationException {
154        try {
155            // create the file if necessary
156            createPath(file);
157            return new FileOutputStream(file);
158        } catch (final FileNotFoundException e) {
159            throw new ConfigurationException("Unable to save to file " + file, e);
160        }
161    }
162
163    @Override
164    public OutputStream getOutputStream(final URL url) throws ConfigurationException {
165        // file URLs have to be converted to Files since FileURLConnection is
166        // read only (https://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4191800)
167        final File file = FileLocatorUtils.fileFromURL(url);
168        if (file != null) {
169            return getOutputStream(file);
170        }
171        // for non file URLs save through an URLConnection
172        OutputStream out;
173        try {
174            final URLConnection connection = url.openConnection();
175            connection.setDoOutput(true);
176
177            // use the PUT method for http URLs
178            if (connection instanceof HttpURLConnection) {
179                final HttpURLConnection conn = (HttpURLConnection) connection;
180                conn.setRequestMethod("PUT");
181            }
182
183            out = connection.getOutputStream();
184
185            // check the response code for http URLs and throw an exception if an error occurred
186            if (connection instanceof HttpURLConnection) {
187                out = new HttpOutputStream(out, (HttpURLConnection) connection);
188            }
189            return out;
190        } catch (final IOException e) {
191            throw new ConfigurationException("Could not save to URL " + url, e);
192        }
193    }
194
195    @Override
196    public String getPath(final File file, final URL url, final String basePath, final String fileName) {
197        String path = null;
198        // if resource was loaded from jar file may be null
199        if (file != null) {
200            path = file.getAbsolutePath();
201        }
202
203        // try to see if file was loaded from a jar
204        if (path == null) {
205            if (url != null) {
206                path = url.getPath();
207            } else {
208                try {
209                    path = getURL(basePath, fileName).getPath();
210                } catch (final Exception e) {
211                    // simply ignore it and return null
212                    if (getLogger().isDebugEnabled()) {
213                        getLogger().debug(String.format("Could not determine URL for " + "basePath = %s, fileName = %s: %s", basePath, fileName, e));
214                    }
215                }
216            }
217        }
218
219        return path;
220    }
221
222    @Override
223    public URL getURL(final String basePath, final String file) throws MalformedURLException {
224        final File f = new File(file);
225        // already absolute?
226        if (f.isAbsolute()) {
227            return FileLocatorUtils.toURL(f);
228        }
229
230        try {
231            if (basePath == null) {
232                return new URL(file);
233            }
234            final URL base = new URL(basePath);
235            return new URL(base, file);
236        } catch (final MalformedURLException uex) {
237            return FileLocatorUtils.toURL(FileLocatorUtils.constructFile(basePath, file));
238        }
239    }
240
241    @Override
242    public URL locateFromURL(final String basePath, final String fileName) {
243        try {
244            final URL url;
245            if (basePath == null) {
246                return new URL(fileName);
247                // url = new URL(name);
248            }
249            final URL baseURL = new URL(basePath);
250            url = new URL(baseURL, fileName);
251
252            // check if the file exists
253            try (InputStream in = url.openStream()) {
254                // nothing
255                in.available();
256            }
257            return url;
258        } catch (final IOException e) {
259            if (getLogger().isDebugEnabled()) {
260                getLogger().debug("Could not locate file " + fileName + " at " + basePath + ": " + e.getMessage());
261            }
262            return null;
263        }
264    }
265}