    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}