blob: 7f1b24fbbca780e56cf5524c0e484ae5b0e45ae7 [file] [log] [blame]
// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file *
// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance *
// * with the License. You may obtain a copy of the License at *
// * *
// * http://www.apache.org/licenses/LICENSE-2.0 *
// * *
// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an *
// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the *
// * specific language governing permissions and limitations under the License. *
// ***************************************************************************************************************************
package org.apache.juneau.microservice.resources;
import static java.util.logging.Level.*;
import static javax.servlet.http.HttpServletResponse.*;
import static org.apache.juneau.html.HtmlDocSerializer.*;
import static org.apache.juneau.http.HttpMethodName.*;
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.logging.*;
import javax.servlet.*;
import org.apache.juneau.annotation.*;
import org.apache.juneau.rest.*;
import org.apache.juneau.rest.annotation.*;
import org.apache.juneau.rest.converters.*;
import org.apache.juneau.transforms.*;
import org.apache.juneau.utils.*;
/**
* REST resource that allows access to a file system directory.
*
* <p>
* The root directory is specified in one of two ways:
* <ul class='spaced-list'>
* <li>
* Specifying the location via a <l>DirectoryResource.rootDir</l> property.
* <li>
* Overriding the {@link #getRootDir()} method.
* </ul>
*
* <p>
* Read/write access control is handled through the following properties:
* <ul class='spaced-list'>
* <li>
* <l>DirectoryResource.allowViews</l> - If <jk>true</jk>, allows view and download access to files.
* <li>
* <l>DirectoryResource.allowPuts</l> - If <jk>true</jk>, allows files to be created or overwritten.
* <li>
* <l>DirectoryResource.allowDeletes</l> - If <jk>true</jk>, allows files to be deleted.
* </ul>
*
* <p>
* Access can also be controlled by overriding the {@link #checkAccess(RestRequest)} method.
*/
@RestResource(
title="File System Explorer",
description="Contents of $RA{path}",
messages="nls/DirectoryResource",
htmldoc=@HtmlDoc(
navlinks={
"up: request:/..",
"options: servlet:/?method=OPTIONS"
}
),
allowedMethodParams="*",
properties={
@Property(name=HTML_uriAnchorText, value="PROPERTY_NAME"),
@Property(name="DirectoryResource.rootDir", value="")
}
)
public class DirectoryResource extends BasicRestServlet {
private static final long serialVersionUID = 1L;
private File rootDir; // The root directory
// Settings enabled through servlet init parameters
boolean allowDeletes, allowPuts, allowViews;
private static Logger logger = Logger.getLogger(DirectoryResource.class.getName());
@Override /* Servlet */
public void init() throws ServletException {
RestContextProperties p = getProperties();
rootDir = new File(p.getString("DirectoryResource.rootDir"));
allowViews = p.getBoolean("DirectoryResource.allowViews", false);
allowDeletes = p.getBoolean("DirectoryResource.allowDeletes", false);
allowPuts = p.getBoolean("DirectoryResource.allowPuts", false);
}
/**
* Returns the root directory defined by the 'rootDir' init parameter.
*
* <p>
* Subclasses can override this method to provide their own root directory.
*
* @return The root directory.
*/
protected File getRootDir() {
if (rootDir == null) {
rootDir = new File(getProperties().getString("rootDir"));
if (! rootDir.exists())
if (! rootDir.mkdirs())
throw new RuntimeException("Could not create root dir");
}
return rootDir;
}
/**
* [GET /*] - On directories, returns a directory listing. On files, returns information about the file.
*
* @param req The HTTP request.
* @return Either a FileResource or list of FileResources depending on whether it's a
* file or directory.
* @throws Exception If file could not be read or access was not granted.
*/
@RestMethod(name=GET, path="/*",
description="On directories, returns a directory listing.\nOn files, returns information about the file.",
converters={Queryable.class}
)
public Object doGet(RestRequest req) throws Exception {
checkAccess(req);
String pathInfo = req.getPathInfo();
File f = pathInfo == null ? rootDir : new File(rootDir.getAbsolutePath() + pathInfo);
if (!f.exists())
throw new RestException(SC_NOT_FOUND, "File not found");
req.setAttribute("path", f.getAbsolutePath());
if (f.isDirectory()) {
List<FileResource> l = new LinkedList<>();
File[] files = f.listFiles();
if (files != null) {
for (File fc : files) {
URL fUrl = new URL(req.getRequestURL().append("/").append(fc.getName()).toString());
l.add(new FileResource(fc, fUrl));
}
}
return l;
}
return new FileResource(f, new URL(req.getRequestURL().toString()));
}
/**
* [DELETE /*] - Delete a file on the file system.
*
* @param req The HTTP request.
* @return The message <js>"File deleted"</js> if successful.
* @throws Exception If file could not be read or access was not granted.
*/
@RestMethod(name=DELETE, path="/*",
description="Delete a file on the file system."
)
public Object doDelete(RestRequest req) throws Exception {
checkAccess(req);
File f = new File(rootDir.getAbsolutePath() + req.getPathInfo());
deleteFile(f);
if (req.getHeader("Accept").contains("text/html"))
return new Redirect();
return "File deleted";
}
/**
* [PUT /*] - Add or overwrite a file on the file system.
*
* @param req The HTTP request.
* @return The message <js>"File added"</js> if successful.
* @throws Exception If file could not be read or access was not granted.
*/
@RestMethod(name=PUT, path="/*",
description="Add or overwrite a file on the file system."
)
public Object doPut(RestRequest req) throws Exception {
checkAccess(req);
File f = new File(rootDir.getAbsolutePath() + req.getPathInfo());
String parentSubPath = f.getParentFile().getAbsolutePath().substring(rootDir.getAbsolutePath().length());
try (InputStream is = req.getInputStream(); OutputStream os = new BufferedOutputStream(new FileOutputStream(f))) {
IOPipe.create(is, os).run();
}
if (req.getContentType().contains("html"))
return new Redirect(parentSubPath);
return "File added";
}
/**
* [VIEW /*] - View the contents of a file.
*
* <p>
* Applies to files only.
*
* @param req The HTTP request.
* @param res The HTTP response.
* @return A Reader containing the contents of the file.
* @throws Exception If file could not be read or access was not granted.
*/
@RestMethod(name="VIEW", path="/*",
description="View the contents of a file.\nApplies to files only."
)
public Reader doView(RestRequest req, RestResponse res) throws Exception {
checkAccess(req);
File f = new File(rootDir.getAbsolutePath() + req.getPathInfo());
if (!f.exists())
throw new RestException(SC_NOT_FOUND, "File not found");
if (f.isDirectory())
throw new RestException(SC_METHOD_NOT_ALLOWED, "VIEW not available on directories");
res.setContentType("text/plain");
return new FileReader(f);
}
/**
* [DOWNLOAD /*] - Download the contents of a file.
*
* <p>
* Applies to files only.
*
* @param req The HTTP request.
* @param res The HTTP response.
* @return A Reader containing the contents of the file.
* @throws Exception If file could not be read or access was not granted.
*/
@RestMethod(name="DOWNLOAD", path="/*",
description="Download the contents of a file.\nApplies to files only."
)
public Reader doDownload(RestRequest req, RestResponse res) throws Exception {
checkAccess(req);
File f = new File(rootDir.getAbsolutePath() + req.getPathInfo());
if (!f.exists())
throw new RestException(SC_NOT_FOUND, "File not found");
if (f.isDirectory())
throw new RestException(SC_METHOD_NOT_ALLOWED, "DOWNLOAD not available on directories");
res.setContentType("application");
return new FileReader(f);
}
/**
* Verify that the specified request is allowed.
*
* <p>
* Subclasses can override this method to provide customized behavior.
* Method should throw a {@link RestException} if the request should be disallowed.
*
* @param req The HTTP request.
*/
protected void checkAccess(RestRequest req) {
String method = req.getMethod();
if (method.equals("VIEW") && ! allowViews)
throw new RestException(SC_METHOD_NOT_ALLOWED, "VIEW not enabled");
if (method.equals("PUT") && ! allowPuts)
throw new RestException(SC_METHOD_NOT_ALLOWED, "PUT not enabled");
if (method.equals("DELETE") && ! allowDeletes)
throw new RestException(SC_METHOD_NOT_ALLOWED, "DELETE not enabled");
if (method.equals("DOWNLOAD") && ! allowViews)
throw new RestException(SC_METHOD_NOT_ALLOWED, "DOWNLOAD not enabled");
}
/** File POJO */
public class FileResource {
private File f;
private URL url;
/**
* Constructor.
*
* @param f The file.
* @param url The URL of the file resource.
*/
public FileResource(File f, URL url) {
this.f = f;
this.url = url;
}
// Bean property getters
/**
* @return The URL of the file resource.
*/
public URL getUrl() {
return url;
}
/**
* @return The file type.
*/
public String getType() {
return (f.isDirectory() ? "dir" : "file");
}
/**
* @return The file name.
*/
public String getName() {
return f.getName();
}
/**
* @return The file size.
*/
public long getSize() {
return f.length();
}
/**
* @return The file last modified timestamp.
*/
@Swap(DateSwap.ISO8601DTP.class)
public Date getLastModified() {
return new Date(f.lastModified());
}
/**
* @return A hyperlink to view the contents of the file.
* @throws Exception If access is not allowed.
*/
public URL getView() throws Exception {
if (allowViews && f.canRead() && ! f.isDirectory())
return new URL(url + "?method=VIEW");
return null;
}
/**
* @return A hyperlink to download the contents of the file.
* @throws Exception If access is not allowed.
*/
public URL getDownload() throws Exception {
if (allowViews && f.canRead() && ! f.isDirectory())
return new URL(url + "?method=DOWNLOAD");
return null;
}
/**
* @return A hyperlink to delete the file.
* @throws Exception If access is not allowed.
*/
public URL getDelete() throws Exception {
if (allowDeletes && f.canWrite())
return new URL(url + "?method=DELETE");
return null;
}
}
/** Utility method */
private void deleteFile(File f) {
try {
if (f.isDirectory()) {
File[] files = f.listFiles();
if (files != null) {
for (File fc : files)
deleteFile(fc);
}
}
f.delete();
} catch (Exception e) {
logger.log(WARNING, "Cannot delete file '" + f.getAbsolutePath() + "'", e);
}
}
}