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