    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}