    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}