diff --git a/src/java/net/brutex/xservices/types/FileInfoType.java b/src/java/net/brutex/xservices/types/FileInfoType.java index 835b921..8221cc7 100644 --- a/src/java/net/brutex/xservices/types/FileInfoType.java +++ b/src/java/net/brutex/xservices/types/FileInfoType.java @@ -17,8 +17,32 @@ package net.brutex.xservices.types; import java.io.File; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URLEncoder; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributeView; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.DosFileAttributeView; +import java.nio.file.attribute.DosFileAttributes; +import java.nio.file.attribute.FileTime; +import java.nio.file.attribute.PosixFileAttributeView; +import java.nio.file.attribute.PosixFileAttributes; +import java.nio.file.attribute.UserPrincipal; +import java.util.GregorianCalendar; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorOrder; +import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlType; + +import com.sun.xml.bind.XmlAccessorFactory; /** * @author Brian Rosenberger, bru(at)brutex.de * @@ -27,69 +51,212 @@ import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement +@XmlType(propOrder={"name", "path", "filesize", "owner", "lastModifiedDate", "createdDate", "lastAccessDate", "mimeType", + "readonly", "hidden", "directory", "symbolicLink", "archive", "system", "downloadUrl"}) +@XmlAccessorType(XmlAccessType.PROPERTY) public class FileInfoType { private String name; private String path; private long filesize; - private boolean canWrite; + private boolean isReadonly; + private boolean isHidden; private boolean isDirectory; + private boolean isSymbolicLink; + private boolean isArchive; + private boolean isSystem; + private GregorianCalendar lastModifiedDate; + private GregorianCalendar createdDate; + private GregorianCalendar lastAccessDate; + private String owner; + private String mimeType; + private URI downloadUrl; + public FileInfoType() { } + + public FileInfoType(Path p) throws IOException { + this.name = p.getFileName().toString(); + this.path = p.toAbsolutePath().toString().replace('\\', '/'); - public FileInfoType(File file) - { - this.name = file.getName(); - this.path = file.getAbsolutePath().replace('\\', '/'); - this.canWrite = file.canWrite(); - this.filesize = file.length(); - this.isDirectory = file.isDirectory(); + + BasicFileAttributeView basicView = Files.getFileAttributeView(p, BasicFileAttributeView.class); + BasicFileAttributes basic; + basic = basicView.readAttributes(); + + this.isDirectory = basic.isDirectory(); + this.isSymbolicLink = basic.isSymbolicLink(); + this.filesize = basic.size(); + this.lastModifiedDate = (GregorianCalendar) GregorianCalendar.getInstance(); + this.lastModifiedDate.setTimeInMillis(basic.lastModifiedTime().toMillis()); + this.createdDate = (GregorianCalendar) GregorianCalendar.getInstance(); + this.createdDate.setTimeInMillis(basic.creationTime().toMillis()); + this.lastAccessDate = (GregorianCalendar) GregorianCalendar.getInstance(); + this.lastAccessDate.setTimeInMillis(basic.lastAccessTime().toMillis()); + + // Try to set the Mime Type for that file + // or default to octet-stream + if(!isDirectory) { + this.mimeType = Files.probeContentType(p); + if(this.mimeType==null) mimeType = "application/octet-stream"; + } else { + this.mimeType = null; + } + + // Set the file/ directory owner + this.owner = Files.getOwner(p).getName(); + + //Dos specific Attributes + DosFileAttributeView dosView = Files.getFileAttributeView(p, DosFileAttributeView.class); + if(dosView != null) { + DosFileAttributes dos = dosView.readAttributes(); + this.isReadonly = dos.isReadOnly(); + this.isHidden = dos.isHidden(); + this.isArchive = dos.isArchive(); + this.isSystem = dos.isSystem(); + } + + //POSIX specific Attributes + PosixFileAttributeView posixView = Files.getFileAttributeView(p, PosixFileAttributeView.class); + if(posixView != null) { + PosixFileAttributes posix = posixView.readAttributes(); + //TODO: Unix specific file attributes + } + } + + public FileInfoType(Path file, URI downloadURL) throws IOException { + this(file); + try { + this.downloadUrl = URI.create(downloadURL+URLEncoder.encode(this.path, "UTF-8")); + } catch (UnsupportedEncodingException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + +/** + * @return the name + */ @XmlElement(name="name") - public String getName() - { - return this.name; - } +public String getName() { + return name; +} - public void setName(String name) - { - this.name = name; - } +/** + * @return the path + */ + @XmlElement(name = "path") +public String getPath() { + return path; +} - @XmlElement(name="path") - public String getPath() - { - return this.path; - } +/** + * @return the filesize + */ +@XmlElement(name="filesize") +public long getFilesize() { + return filesize; +} - public void setPath(String path) - { - this.path = path; - } +/** + * @return the isReadonly + */ +@XmlElement(name="isReadonly") +public boolean isReadonly() { + return isReadonly; +} - @XmlElement(name="size") - public long getFilesize() - { - return this.filesize; - } +/** + * @return the isHidden + */ +@XmlElement(name="isHidden") +public boolean isHidden() { + return isHidden; +} - public void setFilesize(long filesize) - { - this.filesize = filesize; - } +/** + * @return the isDirectory + */ +@XmlElement(name="isDirectory") +public boolean isDirectory() { + return isDirectory; +} - @XmlElement(name="isWritable") - public boolean isCanWrite() - { - return this.canWrite; - } +/** + * @return the isSymbolicLink + */ +@XmlElement(name="isSymbolicLink") +public boolean isSymbolicLink() { + return isSymbolicLink; +} - @XmlElement(name="isDirectory") - public boolean isDirectory() - { - return this.isDirectory; - } +/** + * @return the isArchive + */ +@XmlElement(name="isArchive") +public boolean isArchive() { + return isArchive; +} + +/** + * @return the isSystem + */ +@XmlElement(name="isSystem") +public boolean isSystem() { + return isSystem; +} + +/** + * @return the lastModifiedDate + */ +@XmlElement(name="lastModifiedDate") +public GregorianCalendar getLastModifiedDate() { + return lastModifiedDate; +} + +/** + * @return the createdDate + */ +@XmlElement(name="createdDate") +public GregorianCalendar getCreatedDate() { + return createdDate; +} + +/** + * @return the lastAccessDate + */ +@XmlElement(name="lastAccessDate") +public GregorianCalendar getLastAccessDate() { + return lastAccessDate; +} + +/** + * @return the owner + */ +@XmlElement(name="owner") +public String getOwner() { + return owner; +} + +/** + * @return the mimeType + */ +@XmlElement(name="mimeType") +public String getMimeType() { + return mimeType; +} + +/** + * @return the downloadUrl + */ +@XmlElement(name="downloadUrl") +public URI getDownloadUrl() { + return downloadUrl; +} + + } \ No newline at end of file diff --git a/src/java/net/brutex/xservices/util/FileWalker.java b/src/java/net/brutex/xservices/util/FileWalker.java new file mode 100644 index 0000000..4430695 --- /dev/null +++ b/src/java/net/brutex/xservices/util/FileWalker.java @@ -0,0 +1,148 @@ +/* + * Copyright 2013 Brian Rosenberger (Brutex Network) + * + * Licensed 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 net.brutex.xservices.util; + +import java.io.IOException; +import java.nio.file.FileSystems; +import java.nio.file.FileVisitResult; +import java.nio.file.Path; +import java.nio.file.PathMatcher; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.List; + +import org.apache.log4j.Logger; + +import net.brutex.xservices.types.FileInfoType; + +// TODO: Auto-generated Javadoc +/** + * The Class FileWalker. + * + * @author Brian Rosenberger, bru(at)brutex.de + */ +public class FileWalker extends SimpleFileVisitor { + + /** The matcher. */ + private final PathMatcher matcher; + + /** The num. */ + private long num=0; + + /** The total. */ + private long total=0; + + /** The pattern. */ + private final String pattern; + + /** The logger. */ + final Logger logger = Logger.getLogger(FileWalker.class); + + List list; + + /** + * Instantiates a new file walker. + * + * @param pattern the pattern + */ + public FileWalker(String pattern) { + matcher = FileSystems.getDefault() + .getPathMatcher("glob:" + pattern); + this.pattern = "glob:"+pattern; + this.list = new ArrayList(); + } + + + // Compares the glob pattern against + // the file or directory name. + /** + * Find. + * + * @param file the file + */ + void find(Path file) { + Path name = file.getFileName(); + logger.trace("Compare file " + file.toString() + " against pattern '"+pattern+"'."); + total++; + if (name != null && matcher.matches(name)) { + list.add(file); + logger.debug("Added file " + file.toString() + " to the result set."); + num++; + } + } + + // Invoke the pattern matching + // method on each file. + /* (non-Javadoc) + * @see java.nio.file.SimpleFileVisitor#visitFile(java.lang.Object, java.nio.file.attribute.BasicFileAttributes) + */ + @Override + public FileVisitResult visitFile(Path file, + BasicFileAttributes attrs) { + + find(file); + return FileVisitResult.CONTINUE; + } + + // Invoke the pattern matching + // method on each directory. + /* (non-Javadoc) + * @see java.nio.file.SimpleFileVisitor#preVisitDirectory(java.lang.Object, java.nio.file.attribute.BasicFileAttributes) + */ + @Override + public FileVisitResult preVisitDirectory(Path dir, + BasicFileAttributes attrs) { + find(dir); + return FileVisitResult.CONTINUE; + } + + /* (non-Javadoc) + * @see java.nio.file.SimpleFileVisitor#visitFileFailed(java.lang.Object, java.io.IOException) + */ + @Override + public FileVisitResult visitFileFailed(Path file, + IOException exc) { + logger.warn(String.format("Failed to include file '%s'.", file.toString())); + return FileVisitResult.CONTINUE; + } + + /** + * Gets the count. + * + * @return the count + */ + public long getCount() { + return num; + } + + /** + * Gets the total. + * + * @return the total + */ + public long getTotal() { + return total; + } + + /** + * Get result list + */ + public List getResult() { + return list; + } + +} \ No newline at end of file diff --git a/src/java/net/brutex/xservices/ws/rs/FileInfo.java b/src/java/net/brutex/xservices/ws/rs/FileInfo.java index 2720f32..2bba0f7 100644 --- a/src/java/net/brutex/xservices/ws/rs/FileInfo.java +++ b/src/java/net/brutex/xservices/ws/rs/FileInfo.java @@ -1,5 +1,23 @@ +/* + * Copyright 2013 Brian Rosenberger (Brutex Network) + * + * Licensed 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 net.brutex.xservices.ws.rs; +import java.io.File; + import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.Path; @@ -8,13 +26,56 @@ import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; + +import net.brutex.xservices.ws.XServicesFault; + + +/** + * The FileBrowsing Rest Service. + * + * @author Brian Rosenberger, bru(at)brutex.de + */ @Path("/FileService/") -@Produces({"application/xml"}) -public abstract interface FileInfo -{ - @GET - @Path("getFiles/") - public abstract Response getFiles(@Context HttpHeaders paramHttpHeaders, @QueryParam("directory") String paramString1, @QueryParam("includeDirectories") @DefaultValue("0") boolean paramBoolean1, @QueryParam("includeFiles") @DefaultValue("1") boolean paramBoolean2, @QueryParam("depth") @DefaultValue("1") int paramInt1, @QueryParam("search") String paramString2, @QueryParam("itemsPerPage") @DefaultValue("50") int paramInt2, @QueryParam("page") @DefaultValue("1") int paramInt3); -} +@Produces({ "application/xml" }) +public abstract interface FileInfo { + public final static String BASE_PATH = "/FileService/"; + + /** + * Get the file/ directory listing. + * + * @param paramHttpHeaders the param http headers + * @param uriInfo request url info + * @param directory The directory to list. + * @param includeDirectories Whether or not to include directories in the listing. Default is true. + * @param includeFiles Whether or not to include files in the listing. Default is true. + * @param depth Include subdirectories down to a given depth. Default is 1. + * @param search Additional "Glob search pattern" for the file/ directory name. I.e. '*.log' + * @param itemsPerPage How many items to return with one call. Default is 50. + * @param page Paging support. Default is 1. + * @param useCache whether or not to use cache. Defaults to true. + * @return the FileInfo Set as an XML structure + */ + @GET + @Path("getFiles/") + public abstract Response getFiles( + @Context HttpHeaders paramHttpHeaders, + @Context UriInfo uriInfo, + @QueryParam("directory") String directory, + @QueryParam("includeDirectories") @DefaultValue("0") boolean includeDirectories, + @QueryParam("includeFiles") @DefaultValue("1") boolean includeFiles, + @QueryParam("depth") @DefaultValue("1") int depth, + @QueryParam("search") String search, + @QueryParam("itemsPerPage") @DefaultValue("50") int itemsPerPage, + @QueryParam("page") @DefaultValue("1") int page, + @QueryParam("usecache") @DefaultValue("1") boolean useCache); + + @GET + @Path("getFile/") + //@Produces("application/octet-stream") + public abstract Response getFile( + @Context HttpHeaders paramHttpHeaders, + @QueryParam("file") String file); +} diff --git a/src/java/net/brutex/xservices/ws/rs/FileInfoImpl.java b/src/java/net/brutex/xservices/ws/rs/FileInfoImpl.java index 6e2fe48..ebcbe3b 100644 --- a/src/java/net/brutex/xservices/ws/rs/FileInfoImpl.java +++ b/src/java/net/brutex/xservices/ws/rs/FileInfoImpl.java @@ -1,49 +1,102 @@ +/* + * Copyright 2013 Brian Rosenberger (Brutex Network) + * + * Licensed 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 net.brutex.xservices.ws.rs; import java.io.File; -import java.io.FileFilter; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URI; +import java.nio.file.DirectoryStream; +import java.nio.file.FileSystems; +import java.nio.file.FileVisitOption; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.PathMatcher; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributeView; +import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; +import java.util.EnumSet; import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import javax.ws.rs.NotAuthorizedException; +import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.GenericEntity; import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import net.brutex.xservices.security.StandardSecurityManager; -import net.brutex.xservices.security.UserIdentity; +import javax.ws.rs.core.StreamingOutput; +import javax.ws.rs.core.UriInfo; + +import net.brutex.xservices.security.DirectoryPermission; import net.brutex.xservices.types.FileInfoType; +import net.brutex.xservices.util.FileWalker; import org.apache.jcs.JCS; import org.apache.jcs.access.exception.CacheException; +import org.apache.log4j.Logger; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.authz.UnauthorizedException; /** - * @author Brian Rosenberger, bru(at)brutex.de + * The Class FileInfoImpl. * + * @author Brian Rosenberger, bru(at)brutex.de */ -public class FileInfoImpl implements FileInfo -{ - public Response getFiles(HttpHeaders h, String dir, boolean withDir, boolean withFiles, int level, String search, int count, int page) +public class FileInfoImpl implements FileInfo { + + + Logger logger = Logger.getLogger(FileInfoImpl.class); + + + /* (non-Javadoc) + * @see net.brutex.xservices.ws.rs.FileInfo#getFiles(javax.ws.rs.core.HttpHeaders, java.lang.String, boolean, boolean, int, java.lang.String, int, int) + */ + public Response getFiles(HttpHeaders h, UriInfo uriInfo, String dir, boolean withDir, boolean withFiles, int level, String search, int count, int page, boolean useCache) { - StandardSecurityManager sec = new StandardSecurityManager(); - UserIdentity id = new UserIdentity(); - - if (!sec.canExecute(java.lang.Thread.currentThread().getStackTrace()[1].getMethodName(), id)) { - return null; - } - - System.out.println("Listing directory: " + dir); + + isPermitted(dir); + + URI baseuri = URI.create(uriInfo.getBaseUri()+FileInfo.BASE_PATH+"getFile?file="); + + if(dir==null) {dir = "c:/"; System.out.println("No directory specified.");} + logger.info(String.format("Listing directory '%s'.", dir)); if (level <= 0) level = 1; - if (level > 3) level = 3; + if ((!withDir) && (!withFiles)) withFiles = true; String cachekey = level + "||" + withFiles + "||" + withDir + "||" + search + "||" + dir; try { + logger.debug(String.format("Hitting cache with cachekey '%s'", cachekey)); JCS jcs = JCS.getInstance("FileCache"); - List list = (List)jcs.get(cachekey); - if (list == null) { - list = setDirectory(dir, withDir, withFiles, level, search); + /*Try to retrieve the file list from the cache*/ + List list = (List)jcs.get(cachekey); + + if (list == null || !useCache) { + list = setDirectory(baseuri, dir, withDir, withFiles, level, search); jcs.put(cachekey, list); - System.out.println("Stored in Cache: " + list.toString()); + logger.debug("Stored in Cache: " + list.toString()); } else { - System.out.println("Got from Cache: " + list.toString()); + logger.debug("Got from Cache: " + list.toString()); } int fromIndex = 0; @@ -52,9 +105,8 @@ public class FileInfoImpl implements FileInfo toIndex = page * count; if (toIndex > list.size()) toIndex = list.size(); if (fromIndex > toIndex) fromIndex = toIndex; - GenericEntity sublist = new GenericEntity(list.subList(fromIndex, toIndex)) - { - }; + GenericEntity> sublist = new GenericEntity>(list.subList(fromIndex, toIndex)) {}; + logger.info(String.format("Returning items %s to %s from total of %s items in the list.", fromIndex, toIndex, list.size())); return Response.ok(sublist).build(); } catch (CacheException e) { Response.serverError().build(); @@ -62,34 +114,138 @@ public class FileInfoImpl implements FileInfo return null; } - private void setDirectory(List list, File dir, boolean withDirectories, boolean withFiles, final int depth, final String search) + /** + * Sets the directory. + * + * @param list the list + * @param dir the dir + * @param withDirectories the with directories + * @param withFiles the with files + * @param depth the depth + * @param search the search + */ + private void setDirectory(final URI baseuri, final List list, File dir, boolean withDirectories, boolean withFiles, final int depth, String search) { if (depth <= 0) return; - - File[] files = dir.listFiles(new FileFilter() - { - public boolean accept(File pathname) { - if ((pathname.isDirectory()) && (depth > 1)) return true; - if ((search == null) || (search.equals(""))) return true; - if (!pathname.getAbsolutePath().contains(search)) return false; - return true; - } - }); - if ((dir.getParentFile() != null) && (withDirectories)) list.add(new FileInfoType(dir.getParentFile())); - if (files == null) return; - - for (File e : files) { - if (e.isDirectory()) setDirectory(list, e, withDirectories, withFiles, depth - 1, search); - if (((withDirectories) && (e.isDirectory())) || ( - (withFiles) && (e.isFile()))) - list.add(new FileInfoType(e)); - } + + if(search==null || search.equals("") ) { + search = "*"; + logger.info("No search pattern supplied, using default '*'."); + } + + FileWalker finder = new FileWalker(search); + try { + Files.walkFileTree(dir.toPath(), EnumSet.of(FileVisitOption.FOLLOW_LINKS), depth, finder); + logger.info("FileWalker returned '"+finder.getCount()+"' hits. '" + finder.getTotal() + "' files have been scanned."); + List result = finder.getResult(); + for(Path f : result) { + list.add(new FileInfoType(f, baseuri)); + } + } catch (IOException e2) { + logger.error(e2.getMessage(), e2);; + } } - - private List setDirectory(String dir, boolean withDirectories, boolean withFiles, int depth, String search) + + /** + * Sets the directory. + * + * @param dir the dir + * @param withDirectories the with directories + * @param withFiles the with files + * @param depth the depth + * @param search the search + * @return the list + */ + private List setDirectory(URI baseuri, String dir, boolean withDirectories, boolean withFiles, int depth, String search) { - List list = new ArrayList(); - setDirectory(list, new File(dir), withDirectories, withFiles, depth, search); + List list = new ArrayList(); + setDirectory(baseuri, list, new File(dir), withDirectories, withFiles, depth, search); return list; } + +@Override +public Response getFile(HttpHeaders paramHttpHeaders, String file) { + isPermitted(file); + try { + Path path = FileSystems.getDefault().getPath(file); + + BasicFileAttributeView basicView = Files.getFileAttributeView(path, BasicFileAttributeView.class); + BasicFileAttributes basic; + basic = basicView.readAttributes(); + + + //In case this is a directory + //we zip it and return the zip stream + if(basic.isDirectory()) return getDirectoryAsZip(path); + + + + MediaType mime = MediaType.APPLICATION_OCTET_STREAM_TYPE; + try { + mime = MediaType.valueOf(Files.probeContentType(path)); + } catch (IllegalArgumentException | IOException e) { + //In case we can not find the media type for some reason + //the default assignment is taken, so we can + //ignore this error. + logger.debug(String.format("Could not probe media type for file '%s'. Default is '%s'", path.toString(), mime.getType()), e); + } + Response r = Response.ok(path.toFile(), mime).build(); + String fileName = path.getFileName().toString(); + if(mime == MediaType.APPLICATION_OCTET_STREAM_TYPE) r.getHeaders().add("Content-Disposition", "attachment; filename=\"" + fileName + "\""); + return r; + } catch (IOException e1) { + // TODO Auto-generated catch block + logger.error(e1.getMessage(), e1); + return Response.serverError().build(); + } +} + +private Response getDirectoryAsZip(final Path path) { + + StreamingOutput output = new StreamingOutput() { + + @Override + public void write(OutputStream os) throws IOException, + WebApplicationException { + ZipOutputStream zos = new ZipOutputStream(os); + + //read directory content (files only) + try (DirectoryStream stream = Files.newDirectoryStream(path)) { + for (Path file: stream) { + //skip anything not being a file + if(! file.toFile().isFile()) continue; + + //ZipEntry + String filename = file.getFileName().toString(); + ZipEntry ze = new ZipEntry(filename); + zos.putNextEntry( ze ); + + //read a file and put it into the output stream + FileInputStream fis = new FileInputStream(file.toFile()); + byte[] buffer = new byte[1024]; + int len; + while ((len = fis.read(buffer)) > 0) { + zos.write(buffer, 0, len); + } + zos.flush(); + fis.close(); + } + zos.close(); + } + + } + }; + Response r = Response.ok(output, MediaType.APPLICATION_OCTET_STREAM_TYPE).build(); + String zipname = (path.getFileName()==null) ? "null.zip" : path.getFileName().toString()+".zip"; + r.getHeaders().add("Content-Disposition", "attachment; filename=\"" + zipname + "\""); + return r; +} + +private boolean isPermitted(String dir) { + if(! SecurityUtils.getSubject().isPermitted( new DirectoryPermission(dir))) { + logger.warn(String.format("User '%s' does not have permission to access '%s'.",SecurityUtils.getSubject().getPrincipal(), dir )); + throw new NotAuthorizedException(new UnauthorizedException("User does not have permission to access "+ dir)); + } + return true; +} } \ No newline at end of file