Java tutorial
/* */ package org.taverna.server.master; /* * 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. */ import static javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM; import static javax.ws.rs.core.Response.created; import static javax.ws.rs.core.Response.noContent; import static javax.ws.rs.core.Response.ok; import static javax.ws.rs.core.Response.seeOther; import static javax.ws.rs.core.Response.status; import static org.apache.commons.logging.LogFactory.getLog; import static org.taverna.server.master.api.ContentTypes.APPLICATION_ZIP_TYPE; import static org.taverna.server.master.api.ContentTypes.DIRECTORY_VARIANTS; import static org.taverna.server.master.api.ContentTypes.INITIAL_FILE_VARIANTS; import static org.taverna.server.master.common.Roles.SELF; import static org.taverna.server.master.common.Roles.USER; import static org.taverna.server.master.common.Uri.secure; import static org.taverna.server.master.utils.RestUtils.opt; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URI; import java.util.ArrayList; import java.util.List; import javax.annotation.security.RolesAllowed; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.PathSegment; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.Variant; import javax.xml.ws.Holder; import org.apache.commons.logging.Log; import org.springframework.beans.factory.annotation.Required; import org.taverna.server.master.api.DirectoryBean; import org.taverna.server.master.exceptions.FilesystemAccessException; import org.taverna.server.master.exceptions.NoDirectoryEntryException; import org.taverna.server.master.exceptions.NoUpdateException; import org.taverna.server.master.interfaces.Directory; import org.taverna.server.master.interfaces.DirectoryEntry; import org.taverna.server.master.interfaces.File; import org.taverna.server.master.interfaces.TavernaRun; import org.taverna.server.master.rest.DirectoryContents; import org.taverna.server.master.rest.FileSegment; import org.taverna.server.master.rest.MakeOrUpdateDirEntry; import org.taverna.server.master.rest.MakeOrUpdateDirEntry.MakeDirectory; import org.taverna.server.master.rest.TavernaServerDirectoryREST; import org.taverna.server.master.utils.FilenameUtils; import org.taverna.server.master.utils.CallTimeLogger.PerfLogged; import org.taverna.server.master.utils.InvocationCounter.CallCounted; /** * RESTful access to the filesystem. * * @author Donal Fellows */ class DirectoryREST implements TavernaServerDirectoryREST, DirectoryBean { private Log log = getLog("Taverna.Server.Webapp"); private TavernaServerSupport support; private TavernaRun run; private FilenameUtils fileUtils; @Override public void setSupport(TavernaServerSupport support) { this.support = support; } @Override @Required public void setFileUtils(FilenameUtils fileUtils) { this.fileUtils = fileUtils; } @Override public DirectoryREST connect(TavernaRun run) { this.run = run; return this; } @Override @CallCounted @PerfLogged @RolesAllowed({ USER, SELF }) public Response destroyDirectoryEntry(List<PathSegment> path) throws NoUpdateException, FilesystemAccessException, NoDirectoryEntryException { support.permitUpdate(run); fileUtils.getDirEntry(run, path).destroy(); return noContent().build(); } @Override @CallCounted @PerfLogged @RolesAllowed({ USER, SELF }) public DirectoryContents getDescription(UriInfo ui) throws FilesystemAccessException { return new DirectoryContents(ui, run.getWorkingDirectory().getContents()); } @Override @CallCounted public Response options(List<PathSegment> path) { return opt("PUT", "POST", "DELETE"); } /* * // Nasty! This can have several different responses... * * @Override @CallCounted private Response * getDirectoryOrFileContents(List<PathSegment> path, UriInfo ui, Request * req) throws FilesystemAccessException, NoDirectoryEntryException { * * DirectoryEntry de = fileUtils.getDirEntry(run, path); * * // How did the user want the result? * * List<Variant> variants = getVariants(de); Variant v = * req.selectVariant(variants); if (v == null) return * notAcceptable(variants).type(TEXT_PLAIN) * .entity("Do not know what type of response to produce.") .build(); * * // Produce the content to deliver up * * Object result; if * (v.getMediaType().equals(APPLICATION_OCTET_STREAM_TYPE)) * * // Only for files... * * result = de; else if (v.getMediaType().equals(APPLICATION_ZIP_TYPE)) * * // Only for directories... * * result = ((Directory) de).getContentsAsZip(); else * * // Only for directories... // XML or JSON; let CXF pick what to do * * result = new DirectoryContents(ui, ((Directory) de).getContents()); * return ok(result).type(v.getMediaType()).build(); * * } */ private boolean matchType(MediaType a, MediaType b) { if (log.isDebugEnabled()) log.debug("comparing " + a.getType() + "/" + a.getSubtype() + " and " + b.getType() + "/" + b.getSubtype()); return (a.isWildcardType() || b.isWildcardType() || a.getType().equals(b.getType())) && (a.isWildcardSubtype() || b.isWildcardSubtype() || a.getSubtype().equals(b.getSubtype())); } /** * What are we willing to serve up a directory or file as? * * @param de * The reference to the object to serve. * @return The variants we can serve it as. * @throws FilesystemAccessException * If we fail to read data necessary to detection of its media * type. */ private List<Variant> getVariants(DirectoryEntry de) throws FilesystemAccessException { if (de instanceof Directory) return DIRECTORY_VARIANTS; else if (!(de instanceof File)) throw new FilesystemAccessException("not a directory or file!"); File f = (File) de; List<Variant> variants = new ArrayList<>(INITIAL_FILE_VARIANTS); String contentType = support.getEstimatedContentType(f); if (!contentType.equals(APPLICATION_OCTET_STREAM)) { String[] ct = contentType.split("/"); variants.add(0, new Variant(new MediaType(ct[0], ct[1]), (String) null, null)); } return variants; } /** How did the user want the result? */ private MediaType pickType(HttpHeaders headers, DirectoryEntry de) throws FilesystemAccessException, NegotiationFailedException { List<Variant> variants = getVariants(de); // Manual content negotiation!!! Ugh! for (MediaType mt : headers.getAcceptableMediaTypes()) for (Variant v : variants) if (matchType(mt, v.getMediaType())) return v.getMediaType(); throw new NegotiationFailedException("Do not know what type of response to produce.", variants); } @Override @CallCounted @PerfLogged @RolesAllowed({ USER, SELF }) public Response getDirectoryOrFileContents(List<PathSegment> path, UriInfo ui, HttpHeaders headers) throws FilesystemAccessException, NoDirectoryEntryException, NegotiationFailedException { DirectoryEntry de = fileUtils.getDirEntry(run, path); // How did the user want the result? MediaType wanted = pickType(headers, de); log.info("producing content of type " + wanted); // Produce the content to deliver up Object result; if (de instanceof File) { // Only for files... result = de; List<String> range = headers.getRequestHeader("Range"); if (range != null && range.size() == 1) return new FileSegment((File) de, range.get(0)).toResponse(wanted); } else { // Only for directories... Directory d = (Directory) de; if (wanted.getType().equals(APPLICATION_ZIP_TYPE.getType()) && wanted.getSubtype().equals(APPLICATION_ZIP_TYPE.getSubtype())) result = d.getContentsAsZip(); else // XML or JSON; let CXF pick what to do result = new DirectoryContents(ui, d.getContents()); } return ok(result).type(wanted).build(); } @Override @CallCounted @PerfLogged @RolesAllowed({ USER, SELF }) public Response makeDirectoryOrUpdateFile(List<PathSegment> parent, MakeOrUpdateDirEntry op, UriInfo ui) throws NoUpdateException, FilesystemAccessException, NoDirectoryEntryException { support.permitUpdate(run); DirectoryEntry container = fileUtils.getDirEntry(run, parent); if (!(container instanceof Directory)) throw new FilesystemAccessException("You may not " + ((op instanceof MakeDirectory) ? "make a subdirectory of" : "place a file in") + " a file."); if (op.name == null || op.name.length() == 0) throw new FilesystemAccessException("missing name attribute"); Directory d = (Directory) container; UriBuilder ub = secure(ui).path("{name}"); // Make a directory in the context directory if (op instanceof MakeDirectory) { Directory target = d.makeSubdirectory(support.getPrincipal(), op.name); return created(ub.build(target.getName())).build(); } // Make or set the contents of a file File f = null; for (DirectoryEntry e : d.getContents()) { if (e.getName().equals(op.name)) { if (e instanceof Directory) throw new FilesystemAccessException("You may not overwrite a directory with a file."); f = (File) e; break; } } if (f == null) { f = d.makeEmptyFile(support.getPrincipal(), op.name); f.setContents(op.contents); return created(ub.build(f.getName())).build(); } f.setContents(op.contents); return seeOther(ub.build(f.getName())).build(); } private File getFileForWrite(List<PathSegment> filePath, Holder<Boolean> isNew) throws FilesystemAccessException, NoDirectoryEntryException, NoUpdateException { support.permitUpdate(run); if (filePath == null || filePath.size() == 0) throw new FilesystemAccessException("Cannot create a file that is not in a directory."); List<PathSegment> dirPath = new ArrayList<>(filePath); String name = dirPath.remove(dirPath.size() - 1).getPath(); DirectoryEntry de = fileUtils.getDirEntry(run, dirPath); if (!(de instanceof Directory)) { throw new FilesystemAccessException("Cannot create a file that is not in a directory."); } Directory d = (Directory) de; File f = null; isNew.value = false; for (DirectoryEntry e : d.getContents()) if (e.getName().equals(name)) { if (e instanceof File) { f = (File) e; break; } throw new FilesystemAccessException("Cannot create a file that is not in a directory."); } if (f == null) { f = d.makeEmptyFile(support.getPrincipal(), name); isNew.value = true; } else f.setContents(new byte[0]); return f; } @Override @CallCounted @PerfLogged @RolesAllowed({ USER, SELF }) public Response setFileContents(List<PathSegment> filePath, InputStream contents, UriInfo ui) throws NoDirectoryEntryException, NoUpdateException, FilesystemAccessException { Holder<Boolean> isNew = new Holder<>(true); support.copyStreamToFile(contents, getFileForWrite(filePath, isNew)); if (isNew.value) return created(ui.getAbsolutePath()).build(); else return noContent().build(); } @Override @CallCounted @PerfLogged @RolesAllowed(USER) public Response setFileContentsFromURL(List<PathSegment> filePath, List<URI> referenceList, UriInfo ui) throws NoDirectoryEntryException, NoUpdateException, FilesystemAccessException { support.permitUpdate(run); if (referenceList.isEmpty() || referenceList.size() > 1) return status(422).entity("URI list must have single URI in it").build(); URI uri = referenceList.get(0); try { uri.toURL(); } catch (MalformedURLException e) { return status(422).entity("URI list must have value URL in it").build(); } Holder<Boolean> isNew = new Holder<>(true); File f = getFileForWrite(filePath, isNew); try { support.copyDataToFile(uri, f); } catch (MalformedURLException ex) { // Should not happen; called uri.toURL() successfully above throw new NoUpdateException("failed to parse URI", ex); } catch (IOException ex) { throw new FilesystemAccessException("failed to transfer data from URI", ex); } if (isNew.value) return created(ui.getAbsolutePath()).build(); else return noContent().build(); } }