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