Java tutorial
/* ************************************************************************ ******************* CANADIAN ASTRONOMY DATA CENTRE ******************* ************** CENTRE CANADIEN DE DONNES ASTRONOMIQUES ************** * * (c) 2016. (c) 2016. * Government of Canada Gouvernement du Canada * National Research Council Conseil national de recherches * Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 * All rights reserved Tous droits rservs * * NRC disclaims any warranties, Le CNRC dnie toute garantie * expressed, implied, or nonce, implicite ou lgale, * statutory, of any kind with de quelque nature que ce * respect to the software, soit, concernant le logiciel, * including without limitation y compris sans restriction * any warranty of merchantability toute garantie de valeur * or fitness for a particular marchande ou de pertinence * purpose. NRC shall not be pour un usage particulier. * liable in any event for any Le CNRC ne pourra en aucun cas * damages, whether direct or tre tenu responsable de tout * indirect, special or general, dommage, direct ou indirect, * consequential or incidental, particulier ou gnral, * arising from the use of the accessoire ou fortuit, rsultant * software. Neither the name de l'utilisation du logiciel. Ni * of the National Research le nom du Conseil National de * Council of Canada nor the Recherches du Canada ni les noms * names of its contributors may de ses participants ne peuvent * be used to endorse or promote tre utiliss pour approuver ou * products derived from this promouvoir les produits drivs * software without specific prior de ce logiciel sans autorisation * written permission. pralable et particulire * par crit. * * This file is part of the Ce fichier fait partie du projet * OpenCADC project. OpenCADC. * * OpenCADC is free software: OpenCADC est un logiciel libre ; * you can redistribute it and/or vous pouvez le redistribuer ou le * modify it under the terms of modifier suivant les termes de * the GNU Affero General Public la GNU Affero General Public * License as published by the License? telle que publie * Free Software Foundation, par la Free Software Foundation * either version 3 of the : soit la version 3 de cette * License, or (at your option) licence, soit ( votre gr) * any later version. toute version ultrieure. * * OpenCADC is distributed in the OpenCADC est distribu * hope that it will be useful, dans lespoir quil vous * but WITHOUT ANY WARRANTY; sera utile, mais SANS AUCUNE * without even the implied GARANTIE : sans mme la garantie * warranty of MERCHANTABILITY implicite de COMMERCIALISABILIT * or FITNESS FOR A PARTICULAR ni dADQUATION UN OBJECTIF * PURPOSE. See the GNU Affero PARTICULIER. Consultez la Licence * General Public License for Gnrale Publique GNU Affero * more details. pour plus de dtails. * * You should have received Vous devriez avoir reu une * a copy of the GNU Affero copie de la Licence Gnrale * General Public License along Publique GNU Affero avec * with OpenCADC. If not, see OpenCADC ; si ce nest * <http://www.gnu.org/licenses/>. pas le cas, consultez : * <http://www.gnu.org/licenses/>. * * ************************************************************************ */ package ca.nrc.cadc.beacon.web.resources; import ca.nrc.cadc.auth.AuthMethod; import ca.nrc.cadc.beacon.web.*; import ca.nrc.cadc.beacon.web.restlet.JSONRepresentation; import ca.nrc.cadc.reg.Standards; import ca.nrc.cadc.reg.client.RegistryClient; import ca.nrc.cadc.util.StringUtil; import ca.nrc.cadc.vos.*; import ca.nrc.cadc.vos.client.ClientTransfer; import ca.nrc.cadc.vos.client.VOSpaceClient; import org.apache.commons.fileupload.FileItemIterator; import org.apache.commons.fileupload.FileItemStream; import org.apache.commons.fileupload.FileUploadException; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.json.JSONException; import org.json.JSONWriter; import org.restlet.data.MediaType; import org.restlet.data.Status; import org.restlet.ext.servlet.ServletUtils; import org.restlet.representation.Representation; import org.restlet.resource.Post; import org.restlet.resource.Put; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URL; import java.net.URLEncoder; import java.util.ArrayList; import java.util.List; public class FileItemServerResource extends StorageItemServerResource { private static final int BUFFER_SIZE = 8192; private static final String UPLOAD_FILE_KEY = "upload"; private final UploadVerifier uploadVerifier; private final FileValidator fileValidator; FileItemServerResource(final RegistryClient registryClient, final VOSpaceClient voSpaceClient, final UploadVerifier uploadVerifier, final FileValidator fileValidator) { super(registryClient, voSpaceClient); this.uploadVerifier = uploadVerifier; this.fileValidator = fileValidator; } public FileItemServerResource() { this.uploadVerifier = new UploadVerifier(); this.fileValidator = new FileValidator(); } @Post @Put public void accept(final Representation payload) throws Exception { if ((payload != null) && MediaType.MULTIPART_FORM_DATA.equals(payload.getMediaType(), true)) { // The Apache FileUpload project parses HTTP requests which // conform to RFC 1867, "Form-based File Upload in HTML". That // is, if an HTTP request is submitted using the POST method, // and with a content type of "multipart/form-data", then // FileUpload can parse that request, and get all uploaded files // as FileItem. // Obtain the file upload Representation as an iterator. final ServletFileUpload upload = parseRepresentation(); final FileItemIterator fileItemIterator = upload.getItemIterator(ServletUtils.getRequest(getRequest())); if (!fileItemIterator.hasNext()) { // Some problem occurs, sent back a simple line of text. uploadError(Status.CLIENT_ERROR_BAD_REQUEST, "Unable to upload corrupted or incompatible data."); } else { upload(fileItemIterator); } } else { getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST); getResponse().setEntity("Nothing to upload or invalid data.", MediaType.TEXT_PLAIN); } } protected void upload(final FileItemIterator fileItemIterator) throws IOException, IllegalArgumentException, NodeNotFoundException, NodeAlreadyExistsException { boolean inheritParentPermissions = false; VOSURI newNodeURI = null; try { while (fileItemIterator.hasNext()) { final FileItemStream nextFileItemStream = fileItemIterator.next(); if (nextFileItemStream.getFieldName().startsWith(UPLOAD_FILE_KEY)) { newNodeURI = upload(nextFileItemStream); } else if (nextFileItemStream.getFieldName().equals("inheritPermissionsCheckBox")) { inheritParentPermissions = true; } } } catch (FileUploadException e) { throw new IOException(e); } if (inheritParentPermissions) { setInheritedPermissions(newNodeURI); } } /** * Perform the actual upload. * * @param fileItemStream The upload file item stream. * @return The URI to the new node. * @throws IOException If anything goes wrong. */ VOSURI upload(final FileItemStream fileItemStream) throws IOException, IllegalArgumentException, NodeAlreadyExistsException { final String filename = fileItemStream.getName(); if (fileValidator.validateString(filename)) { final String path = getCurrentItemURI().getPath() + "/" + URLEncoder.encode(filename, "UTF-8"); final DataNode dataNode = new DataNode(toURI(path)); // WebRT 19564: Add content type to the response of // uploaded items. final List<NodeProperty> properties = new ArrayList<>(); properties.add(new NodeProperty(VOS.PROPERTY_URI_TYPE, fileItemStream.getContentType())); properties.add(new NodeProperty(VOS.PROPERTY_URI_CONTENTLENGTH, Long.toString(getRequest().getEntity().getSize()))); dataNode.setProperties(properties); try (final InputStream inputStream = fileItemStream.openStream()) { upload(inputStream, dataNode); } return dataNode.getUri(); } else { throw new IllegalArgumentException("Name is required and cannot contain characters \n" + "outside of alphanumeric and _-()=+!,;:@&*$."); } } /** * Do the secure upload. * * @param inputStream The InputStream to pull from. * @param dataNode The DataNode to upload to. */ protected void upload(final InputStream inputStream, final DataNode dataNode) throws NodeAlreadyExistsException, IOException { final String path = dataNode.getUri().getPath(); final UploadOutputStreamWrapper outputStreamWrapper = new UploadOutputStreamWrapperImpl(inputStream, BUFFER_SIZE); try { // Due to a bug in VOSpace that returns a 400 while checking // for an existing Node, we will work around it by checking manually // rather than looking for a NodeNotFoundException as expected, and // return the 409 code, while maintaining backward compatibility // with the catch below. // jenkinsd 2016.07.25 if (voSpaceClient.getNode(path) != null) { throw new NodeAlreadyExistsException(path); } } catch (NodeNotFoundException e) { createNode(dataNode, false); } try { upload(outputStreamWrapper, dataNode); } catch (Exception e) { String message = null; if ((e.getCause() != null) && StringUtil.hasText(e.getCause().getMessage())) { message = e.getCause().getMessage(); } else if (StringUtil.hasText(e.getMessage())) { message = e.getMessage(); } if (message == null) { message = "Error during upload."; } uploadError(Status.SERVER_ERROR_INTERNAL, message); } } /** * Abstract away the Transfer stuff. It's cumbersome. * * @param outputStreamWrapper The OutputStream wrapper. * @param dataNode The node to upload. * @throws Exception To capture transfer and upload failures. */ void upload(final UploadOutputStreamWrapper outputStreamWrapper, final DataNode dataNode) throws Exception { final RegistryClient registryClient = new RegistryClient(); final URL baseURL = registryClient.getServiceURL(dataNode.getUri().getServiceURI(), Standards.VOSPACE_TRANSFERS_20, AuthMethod.ANON); final List<Protocol> protocols = new ArrayList<>(); protocols.add(new Protocol(VOS.PROTOCOL_HTTP_PUT, baseURL.toString(), null)); final Transfer transfer = new Transfer(dataNode.getUri().getURI(), Direction.pushToVoSpace, new View(URI.create(VOS.VIEW_DEFAULT)), protocols); transfer.version = VOS.VOSPACE_21; final ClientTransfer ct = voSpaceClient.createTransfer(transfer); ct.setOutputStreamWrapper(outputStreamWrapper); ct.runTransfer(); final Node uploadedNode = getNode(dataNode.getUri(), VOS.Detail.properties); uploadVerifier.verifyByteCount(outputStreamWrapper.getByteCount(), uploadedNode); uploadVerifier.verifyMD5(outputStreamWrapper.getCalculatedMD5(), uploadedNode); uploadSuccess(); } /** * Parse the representation into a Map for easier access to Form elements. * * @return Map of field names to File Items, or empty Map. * Never null. * @throws Exception If the Upload could not be parsed. */ private ServletFileUpload parseRepresentation() throws Exception { // 1/ Create a factory for disk-based file items final DiskFileItemFactory factory = new DiskFileItemFactory(); factory.setSizeThreshold(1000240); // Create a new file upload handler. return createFileUpload(factory); } /** * External method of obtaining a Restlet File Upload. * * @param factory Factory used to create the upload. * @return RestletFileUpload instance. */ private ServletFileUpload createFileUpload(final DiskFileItemFactory factory) { return new ServletFileUpload(factory); } private void uploadError(final Status status, final String message) { writeResponse(status, new JSONRepresentation() { @Override public void write(final JSONWriter jsonWriter) throws JSONException { jsonWriter.object().key("error").value(message).endObject(); } }); } private void uploadSuccess() { writeResponse(Status.SUCCESS_CREATED, new JSONRepresentation() { @Override public void write(final JSONWriter jsonWriter) throws JSONException { jsonWriter.object().key("code").value(0).endObject(); } }); } }