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