All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jetty.util.resource.Resource Maven / Gradle / Ivy

There is a newer version: 12.1.0.alpha1
Show newest version
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.util.resource;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.FileAttribute;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;

import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 

* A Resource is a wrapper over a {@link Path} object pointing to a file or directory that can be represented by a{@link java.nio.file.FileSystem}. *

*

* Supports real filesystems, and also ZipFS. *

*/ public abstract class Resource implements Iterable { private static final Logger LOG = LoggerFactory.getLogger(Resource.class); private static final LinkOption[] NO_FOLLOW_LINKS = new LinkOption[]{LinkOption.NOFOLLOW_LINKS}; public static String dump(Resource resource) { if (resource == null) return "null exists=false directory=false lm=-1"; return "%s exists=%b directory=%b lm=%s" .formatted(resource.toString(), resource.exists(), resource.isDirectory(), resource.lastModified()); } /** * Return the Path corresponding to this resource. * * @return the path or null if there is no Path representation. */ public abstract Path getPath(); /** * Return true if this resource is contained in the Resource r, either because * r is a folder or a jar file or any form of resource capable of containing other resources. * * @param container the containing resource * @return true if this Resource is contained, false otherwise * @see #contains(Resource) */ public boolean isContainedIn(Resource container) { return container != null && container.contains(this); } /** * Return true if this resource deeply contains the other Resource. This resource must be * a directory or a jar file or any form of resource capable of containing other resources. * * @param other the resource * @return true if this Resource is deeply contains the other Resource, false otherwise * @see #isContainedIn(Resource) */ public boolean contains(Resource other) { if (other == null) return false; URI thisURI = getURI(); if (thisURI == null) throw new UnsupportedOperationException("Resources without a URI must implement contains"); URI otherURI = other.getURI(); if (otherURI == null) return false; // Different schemes? not a chance it contains the other if (!StringUtil.asciiEqualsIgnoreCase(thisURI.getScheme(), otherURI.getScheme())) return false; // Different authorities? not a valid contains() check if (!Objects.equals(thisURI.getAuthority(), otherURI.getAuthority())) return false; // Ensure that if `file` scheme is used, it's using a consistent convention to allow for startsWith check String thisURIString = URIUtil.correctURI(thisURI).toASCIIString(); String otherURIString = URIUtil.correctURI(otherURI).toASCIIString(); return otherURIString.startsWith(thisURIString) && (thisURIString.length() == otherURIString.length() || otherURIString.charAt(thisURIString.length()) == '/'); } /** * Get the relative path from this Resource to a possibly contained resource. * @param other The other resource that may be contained in this resource * @return a relative Path representing the path from this resource to the other resource, * or null if not able to represent other resources as relative to this resource */ public Path getPathTo(Resource other) { Path thisPath = getPath(); if (thisPath == null) throw new UnsupportedOperationException("Resources without a Path must implement getPathTo"); if (!contains(other)) return null; Path otherPath = other.getPath(); if (otherPath == null) return null; return thisPath.relativize(otherPath); } /** *

Return an Iterator of all Resource's referenced in this Resource.

*

This is meaningful if you have a {@link CombinedResource}, * otherwise it will be a single entry Iterator of this resource.

* * @return the iterator of Resources. */ @Override public Iterator iterator() { return List.of(this).iterator(); } /** * Equivalent to {@link Files#exists(Path, LinkOption...)} with the following parameters: * {@link #getPath()} and {@link LinkOption#NOFOLLOW_LINKS}. * * @return true if the represented resource exists. */ public boolean exists() { return Files.exists(getPath(), NO_FOLLOW_LINKS); } /** * Return true if resource represents a directory of potential resources. * * @return true if the represented resource is a container/directory. */ public abstract boolean isDirectory(); /** * True if the resource is readable. * * @return true if the represented resource exists, and can read from. */ public abstract boolean isReadable(); /** * The time the resource was last modified. * * @return the last modified time instant, or {@link Instant#EPOCH} if unable to obtain last modified. */ public Instant lastModified() { return Instant.EPOCH; } /** * Length of the resource. * * @return the length of the resource in bytes, or -1L if unable to provide a size (such as a directory resource). */ public long length() { return -1L; } /** * URI representing the resource. * * @return a URI representing the given resource, or null if there is no URI representation of the resource. */ public abstract URI getURI(); /** * The full name of the resource. * * @return the full name of the resource, or null if there is no name for the resource. */ public abstract String getName(); /** *

The file name of the resource.

* *

This is the last segment of the path.

* * @return the filename of the resource, or "" if there are no path segments (eg: path of "/"), or null if resource * cannot determine a filename. * @see Path#getFileName() */ public abstract String getFileName(); /** * Creates a new input stream to the resource. * * @return an input stream to the resource or null if one is not available. * @throws IOException if there is a problem opening the input stream */ public InputStream newInputStream() throws IOException { Path path = getPath(); if (path == null) return null; return Files.newInputStream(path, StandardOpenOption.READ); } /** * Readable ByteChannel for the resource. * * @return a readable {@link java.nio.channels.ByteChannel} to the resource or null if one is not available. * @throws IOException if unable to open the readable bytechannel for the resource. * @deprecated use {@link #newInputStream()} or {@code IOResources} instead. */ @Deprecated(since = "12.0.8", forRemoval = true) public ReadableByteChannel newReadableByteChannel() throws IOException { Path path = getPath(); if (path == null) return null; return Files.newByteChannel(getPath(), StandardOpenOption.READ); } /** *

List of contents of a directory {@link Resource}.

* *

Ordering is {@link java.nio.file.FileSystem} dependent, so callers may wish to sort the return value to ensure deterministic behavior.

* * @return a mutable list of resources contained in the directory resource, * or an empty immutable list if unable to build the list (e.g. the resource is not a directory or not readable). * @see Resource#isDirectory() * @see Resource#isReadable() */ public List list() { return List.of(); // empty } /** * Resolve an existing Resource. * * @param subUriPath the encoded subUriPath * @return a Resource representing the requested subUriPath, which may not {@link #exists() exist}, * or null if the resource cannot exist. * @throws IllegalArgumentException if subUriPath is invalid */ public abstract Resource resolve(String subUriPath); /** * @return true if this Resource is an alias to another real Resource */ public boolean isAlias() { return false; } /** *

The real URI of the resource.

*

If this Resource is an alias, ({@link #isAlias()}), this * URI will be different from {@link #getURI()}, and will point to the real name/location * of the Resource.

* * @return The real URI location of this resource. */ public URI getRealURI() { return getURI(); } /** * Copy the Resource to the new destination file or directory. * *

If this Resource is a File:

*
    *
  • And the {@code destination} does not exist then {@link IO#copyFile(Path, Path)} is used.
  • *
  • And the {@code destination} is a File then {@link IO#copyFile(Path, Path)} is used.
  • *
  • And the {@code destination} is a Directory then * a new {@link Path} reference is created in the destination with the same * filename as this Resource, which is used via {@link IO#copyFile(Path, Path)}.
  • *
* *

If this Resource is a Directory:

*
    *
  • And the {@code destination} does not exist then * the destination is created as a directory via {@link Files#createDirectories(Path, FileAttribute[])} * before the {@link IO#copyDir(Path, Path)} method is used.
  • *
  • And the {@code destination} is a File then this results in an {@link IllegalArgumentException}.
  • *
  • And the {@code destination} is a Directory then all files in this Resource * directory tree are copied to the {@code destination}, using {@link IO#copyFile(Path, Path)} * maintaining the same directory structure.
  • *
* *

If this Resource is not backed by a {@link Path}, use {@link #newInputStream()}:

*
    *
  • And the {@code destination} does not exist, copy {@link InputStream} * to the {@code destination} as a new file.
  • *
  • And the {@code destination} is a File, copy {@link InputStream} * to the existing {@code destination} file.
  • *
  • And the {@code destination} is a Directory, copy {@link InputStream} * to a new {@code destination} file in the destination directory * based on this the result of {@link #getFileName()} as the filename.
  • *
* * @param destination the destination file or directory to use (created if it does not exist). * @throws IOException if unable to copy the resource */ public void copyTo(Path destination) throws IOException { if (!exists()) throw new IOException("Resource does not exist: " + getFileName()); Path src = getPath(); if (src == null) { // this implementation is not backed by a Path. // is this a Directory? if (isDirectory()) { // if we reached this point, we have a Resource implementation that needs custom copyTo. throw new UnsupportedOperationException("Directory Resources without a Path must implement copyTo: " + this); } // assume that this Resource is a File. String filename = getFileName(); if (StringUtil.isBlank(filename)) { throw new UnsupportedOperationException("File Resources without a Path must implement getFileName: " + this); } Path destFile = destination; if (Files.isDirectory(destFile)) { destFile = destFile.resolve(filename); } // use old school stream based copy (without a Path) try (InputStream in = newInputStream(); // use non-path newInputStream OutputStream out = Files.newOutputStream(destFile, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) { IO.copy(in, out); } return; } // Is this a File resource? if (Files.isRegularFile(src)) { if (Files.isDirectory(destination)) { // to a directory, preserve the filename Path destPath = destination.resolve(src.getFileName().toString()); IO.copyFile(src, destPath); } else { // to a file, use destination as-is IO.copyFile(src, destination); } return; } // At this point this PathResource is a directory, // wanting to copy to a destination directory (that might not exist yet) assert isDirectory(); IO.copyDir(src, destination); } /** * Get a deep collection of contained resources. * @return A collection of all Resources deeply contained within this resource if it is a directory, * otherwise an empty collection is returned. */ public Collection getAllResources() { try { List children = list(); if (children == null || children.isEmpty()) return List.of(); boolean noDepth = true; for (Iterator i = children.iterator(); noDepth && i.hasNext(); ) { Resource resource = i.next(); if (resource.isDirectory()) { // If the directory is a symlink we do not want to go any deeper. Path resourcePath = resource.getPath(); if (resourcePath == null || !Files.isSymbolicLink(resourcePath)) noDepth = false; } } if (noDepth) return children; ArrayList deep = new ArrayList<>(); for (Resource r: children) { deep.add(r); if (r.isDirectory()) deep.addAll(r.getAllResources()); } return deep; } catch (Exception e) { throw new IllegalStateException(e); } } public boolean isSameFile(Path path) { Path resourcePath = getPath(); if (Objects.equals(path, resourcePath)) return true; try { if (Files.isSameFile(path, resourcePath)) return true; } catch (Throwable t) { if (LOG.isDebugEnabled()) LOG.debug("ignored", t); } return false; } public String toString() { String str = getName(); URI uri = getURI(); if (uri != null) str = getURI().toASCIIString(); return "(" + this.getClass().getSimpleName() + ") " + str; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy