001/*
002 * Copyright 2023 the original author or authors.
003 * <p>
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 * <p>
008 * https://www.apache.org/licenses/LICENSE-2.0
009 * <p>
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package de.cuioss.tools.io;
017
018import static de.cuioss.tools.base.Preconditions.checkArgument;
019
020import java.io.IOException;
021import java.io.InputStream;
022import java.net.MalformedURLException;
023import java.net.URL;
024import java.net.URLConnection;
025import java.util.Optional;
026import java.util.concurrent.TimeUnit;
027
028import de.cuioss.tools.logging.CuiLogger;
029import lombok.EqualsAndHashCode;
030import lombok.ToString;
031
032/**
033 * This {@link FileLoader} takes a {@link URL} as its parameter which is useful
034 * when e.g. iterating over an enumeration of URLs from
035 * {@link ClassLoader#getResources(String)}. It converts the given URL to a
036 * {@code String} and prefixes it with {@link FileTypePrefix#URL}.
037 *
038 * @author Sven Haag
039 */
040@EqualsAndHashCode(of = { "url" })
041@ToString(of = { "url" })
042public class UrlLoader implements FileLoader {
043
044    private static final long serialVersionUID = -8758614099334823819L;
045
046    private static final CuiLogger log = new CuiLogger(UrlLoader.class);
047
048    private final URL url;
049    private transient URLConnection connection;
050
051    /**
052     * @param url representing a valid URL
053     * @throws IllegalArgumentException indicating that the given String represents
054     *                                  a valid URL
055     */
056    public UrlLoader(final String url) {
057        var sanitizedUrl = url;
058        if (FileTypePrefix.URL.is(url)) {
059            sanitizedUrl = FileTypePrefix.URL.removePrefix(url);
060        }
061
062        try {
063            this.url = new URL(sanitizedUrl);
064        } catch (final MalformedURLException e) {
065            throw new IllegalArgumentException("Provided URL is invalid: " + url, e);
066        }
067    }
068
069    /**
070     * @param url hopefully a JAR URL
071     */
072    public UrlLoader(final URL url) {
073        checkArgument(null != url, "url must not be null");
074        this.url = url;
075    }
076
077    /**
078     * @return true, if a connection to {@link #getURL()} can be established
079     */
080    @Override
081    public boolean isReadable() {
082        try {
083            var con = getConnection();
084            if (con.isPresent()) {
085                con.get().connect();
086                return true;
087            }
088        } catch (final IOException e) {
089            log.error(e, "Could not read from URL: {}", getURL());
090        }
091        return false;
092    }
093
094    @Override
095    public StructuredFilename getFileName() {
096        return new StructuredFilename(getURL().getPath());
097    }
098
099    @Override
100    public InputStream inputStream() throws IOException {
101        var con = getConnection();
102        if (con.isPresent()) {
103            return con.get().getInputStream();
104        }
105        return null;
106    }
107
108    @Override
109    public URL getURL() {
110        return url;
111    }
112
113    @Override
114    public boolean isFilesystemLoader() {
115        return false;
116    }
117
118    private Optional<URLConnection> getConnection() {
119        if (null == connection) {
120            try {
121                connection = url.openConnection();
122                connection.setConnectTimeout((int) TimeUnit.SECONDS.toMillis(5));
123                connection.setReadTimeout((int) TimeUnit.SECONDS.toMillis(5));
124            } catch (final IOException e) {
125                log.error(e, "Portal-538: Could not read from URL: {}", getURL());
126            }
127        }
128        return Optional.ofNullable(connection);
129    }
130}