Java tutorial
/** * CellProfiler is distributed under the GNU General Public License. * See the accompanying file README for details. * * Developed by the Broad Institute * Copyright 2003-2011 * Website: http://www.cellprofiler.org */ package org.cellprofiler.subimager; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URI; import java.net.URISyntaxException; import java.util.Arrays; import java.util.List; import loci.common.DataTools; import loci.common.services.DependencyException; import loci.common.services.ServiceException; import loci.common.services.ServiceFactory; import loci.formats.FormatException; import loci.formats.ImageWriter; import loci.formats.meta.IMetadata; import loci.formats.services.OMEXMLService; import ome.xml.model.enums.PixelType; import org.apache.commons.fileupload.FileItemIterator; import org.apache.commons.fileupload.FileItemStream; import org.apache.commons.fileupload.FileUpload; import org.apache.commons.fileupload.FileUploadException; import org.apache.commons.fileupload.MultipartStream.MalformedStreamException; import org.apache.commons.fileupload.RequestContext; import org.apache.commons.lang.StringEscapeUtils; import org.apache.log4j.Logger; import static org.cellprofiler.subimager.SubimagerUtils.reportError; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; /** * @author Lee Kamentsky * * The writer handler accepts a POST request with a multipart/form-data content type. * * The following parts are required: * * An image encoded to the standards described in {@link org.cellprofiler.subimager.NDImage} * Content-Disposition: form-data; name="image" * Content-Type: application/octet-stream * * OME-XML (see http://git.openmicroscopy.org/src/develop/components/specification/Documentation/Generated/OME-2011-06/ome.html) * Content-Disposition: form-data; name="omexml" * Content-Type: text/xml * * The URL-encoded URL of the target location for the file: * Content-Disposition: form-data; name="url" * * The following parts are optional: * * The index number of the plane being saved: * Content-Disposition: form-data; name="index" * * Compression: * Content-Disposition: form-data; name="compression" * * Compression choices are somewhat ad-hoc with "uncompressed" being * supported by all. Others are LZW, zlib and JPEG2000. * * The image should be scaled to match the range of the data type chosen inside the OME-XML. * For instance, if saved in UINT8, the image should have values between 0 and 255.0. * Values below the range of the data type chosen are stored as the type minimum * and values above the range are stored as the type maximum. */ @SuppressWarnings("restriction") public class ImageWriterHandler implements HttpHandler { static final private String MULTIPART_FORM_DATA = "multipart/form-data"; static final private String BOUNDARY_EQUALS = "boundary="; static final private String NAME_IMAGE = "image"; static final private String NAME_OMEXML = "omexml"; static final private String NAME_URL = "url"; static final private String NAME_INDEX = "index"; static final private String NAME_COMPRESSION = "compression"; static final private String DEFAULT_COMPRESSION = "default"; static final private String TYPE_BINARY = "application/octet-stream"; static final private List<PixelType> SUPPORTED_PIXEL_TYPES = Arrays .asList(new PixelType[] { PixelType.DOUBLE, PixelType.FLOAT, PixelType.INT8, PixelType.INT16, PixelType.INT32, PixelType.UINT8, PixelType.UINT16, PixelType.UINT32 }); static final private String[] PREFERRED_COMPRESSION = { "LZW", "JPEG2000", "zlib" }; static final Logger logger = Logger.getLogger(ImageWriterHandler.class); /* (non-Javadoc) * @see com.sun.net.httpserver.HttpHandler#handle(com.sun.net.httpserver.HttpExchange) */ public void handle(HttpExchange exchange) throws IOException { if (!exchange.getRequestMethod().equals("POST")) { reportError(exchange, HttpURLConnection.HTTP_BAD_METHOD, "<html><body>writeimage only accepts HTTP post</body></html>"); return; } final String contentType = exchange.getRequestHeaders().getFirst("Content-Type"); if (contentType == null) { reportError(exchange, HttpURLConnection.HTTP_BAD_REQUEST, "<html><body>No Content-type request header</body></html>"); return; } if (!contentType.startsWith(MULTIPART_FORM_DATA)) { reportError(exchange, HttpURLConnection.HTTP_BAD_REQUEST, "<html><body>Content type must be " + MULTIPART_FORM_DATA + ".</body></html>"); return; } int idx = contentType.indexOf(BOUNDARY_EQUALS, MULTIPART_FORM_DATA.length()); if (idx == -1) { reportError(exchange, HttpURLConnection.HTTP_BAD_REQUEST, "<html><body>Did not find boundary identifier in multipart/form-data content type.</body></html>"); return; } final String contentEncoding = exchange.getRequestHeaders().getFirst("Content-Encoding"); String contentLengthString = exchange.getRequestHeaders().getFirst("Content-Length"); if (contentLengthString == null) { reportError(exchange, HttpURLConnection.HTTP_BAD_REQUEST, "<html><body>No Content-Length request header</body></html>"); return; } try { Integer.parseInt(contentLengthString); } catch (NumberFormatException e) { reportError(exchange, HttpURLConnection.HTTP_BAD_REQUEST, String .format("<html><body>Content length was not a number: %s</body></html>", contentLengthString)); return; } final int contentLength = Integer.parseInt(contentLengthString); final InputStream requestBodyStream = exchange.getRequestBody(); FileUpload upload = new FileUpload(); FileItemIterator fileItemIterator; String omeXML = null; URI uri = null; int index = 0; NDImage ndimage = null; String compression = DEFAULT_COMPRESSION; try { fileItemIterator = upload.getItemIterator(new RequestContext() { public String getCharacterEncoding() { return contentEncoding; } public String getContentType() { return contentType; } public int getContentLength() { return contentLength; } public InputStream getInputStream() throws IOException { return requestBodyStream; } }); while (fileItemIterator.hasNext()) { FileItemStream fis = fileItemIterator.next(); String name = fis.getFieldName(); if (name.equals(NAME_IMAGE)) { String imageContentType = fis.getContentType(); if (imageContentType == null) { reportError(exchange, HttpURLConnection.HTTP_UNSUPPORTED_TYPE, "<html><body>Image form-data field must have a content type.</body></html>"); return; } try { InputStream is = SubimagerUtils.getInputStream(exchange, fis); if (is == null) return; ndimage = NDImage.decode(is); } catch (MalformedStreamException e) { reportError(exchange, HttpURLConnection.HTTP_BAD_REQUEST, "<html><body>Failed to read body for part, " + name + ".</body></html>"); } catch (IOException e) { reportError(exchange, HttpURLConnection.HTTP_INTERNAL_ERROR, "<html><body>Failed to read multipart body data</body></html>"); return; } catch (org.cellprofiler.subimager.NDImage.FormatException e) { reportError(exchange, HttpURLConnection.HTTP_BAD_REQUEST, "<html><body>Image data was not in correct format: " + e.getMessage() + "</body></html>"); return; } } else { String partDataString = SubimagerUtils.readFully(exchange, fis); if (partDataString == null) return; if (name.equals(NAME_INDEX)) { try { index = Integer.parseInt(partDataString); } catch (NumberFormatException e) { reportError(exchange, HttpURLConnection.HTTP_BAD_REQUEST, "<html><body>Index form-data field must be a number: " + partDataString + ".</body></html>"); return; } } else if (name.equals(NAME_OMEXML)) { omeXML = partDataString; } else if (name.equals(NAME_URL)) { try { uri = new URI(partDataString); } catch (URISyntaxException e) { reportError(exchange, HttpURLConnection.HTTP_BAD_REQUEST, "<html><body>Improperly formatted URL: " + partDataString + ".</body></html>"); return; } } else if (name.equals(NAME_COMPRESSION)) { compression = partDataString; } } } if (ndimage == null) { reportError(exchange, HttpURLConnection.HTTP_BAD_REQUEST, "<html><body>Request did not have an image part</body></html>"); return; } if (uri == null) { reportError(exchange, HttpURLConnection.HTTP_BAD_REQUEST, "<html><body>Request did not have a URL part</body></html>"); return; } if (omeXML == null) { reportError(exchange, HttpURLConnection.HTTP_BAD_REQUEST, "<html><body>Request did not have an omexml part</body></html>"); return; } writeImage(exchange, ndimage, uri, omeXML, index, compression); } catch (FileUploadException e) { reportError(exchange, HttpURLConnection.HTTP_BAD_REQUEST, String .format("<html><body>Parsing error in multipart message: %s</body></html>", e.getMessage())); return; } } /** * Write the image plane to the given uri * * @param exchange the HttpExchange for the connection * @param ndimage the image to write * @param uri the file URI to write to (must be a file URI currently) * @param omeXML the OME-XML metadata for the plane * @param index the planar index of the plane being written to the file. Note that the indices must be written in order and that an index of 0 will truncate the file. * @param compression the compression method to be used * @throws IOException */ private void writeImage(HttpExchange exchange, NDImage ndimage, URI uri, String omeXML, int index, String compression) throws IOException { if (!uri.getScheme().equals("file")) { reportError(exchange, HttpURLConnection.HTTP_NOT_IMPLEMENTED, "<html><body>This server currently only supports the file: protocol, url=" + uri.toString() + "</body></html>"); return; } File outputFile = new File(uri); if ((index == 0) && outputFile.exists()) { outputFile.delete(); } IMetadata metadata = null; try { ServiceFactory factory; factory = new ServiceFactory(); OMEXMLService service = factory.getInstance(OMEXMLService.class); metadata = service.createOMEXMLMetadata(omeXML); } catch (DependencyException e) { reportError(exchange, HttpURLConnection.HTTP_INTERNAL_ERROR, "<html><body>Configuration error: could not create OMEXML service - check for missing omexml libraries</body></html>"); return; } catch (ServiceException e) { reportError(exchange, HttpURLConnection.HTTP_INTERNAL_ERROR, "<html><body>Possible OME-XML parsing error: " + e.getMessage() + "</body></html>"); return; } ImageWriter writer = new ImageWriter(); writer.setMetadataRetrieve(metadata); try { writer.setId(outputFile.getAbsolutePath()); } catch (IOException e) { reportError(exchange, HttpURLConnection.HTTP_INTERNAL_ERROR, "<html><body>Failed to open output file " + outputFile.getAbsolutePath() + "</body></html>"); return; } catch (FormatException e) { reportError(exchange, HttpURLConnection.HTTP_INTERNAL_ERROR, "<html><body>Format exception when opening output file: " + e.getMessage() + "</body></html>"); return; } PixelType pixelType = metadata.getPixelsType(0); if (SUPPORTED_PIXEL_TYPES.indexOf(pixelType) == -1) { reportError(exchange, HttpURLConnection.HTTP_BAD_REQUEST, "<html><body>Unsupported pixel type: " + pixelType.getValue() + "</body></html>"); return; } boolean toBigEndian = metadata.getPixelsBinDataBigEndian(0, 0); writer.setInterleaved(true); List<String> compressionTypes = Arrays.asList(writer.getCompressionTypes()); try { if (compression == DEFAULT_COMPRESSION) { for (String possibleCompression : PREFERRED_COMPRESSION) { if (compressionTypes.indexOf(possibleCompression) != -1) { //writer.setCompression(possibleCompression); break; } } } else { if (compressionTypes.indexOf(compression) == -1) { reportError(exchange, HttpURLConnection.HTTP_BAD_REQUEST, "<html><body>Unsupported compression type: " + compression + "</body></html>"); return; } writer.setCompression(compression); } } catch (FormatException e) { reportError(exchange, HttpURLConnection.HTTP_INTERNAL_ERROR, "<html><body>Error when setting compression type: " + e.getMessage() + "</body></html>"); return; } byte[] buffer = convertImage(ndimage, pixelType, toBigEndian); try { writer.saveBytes(index, buffer); writer.close(); } catch (IOException e) { reportError(exchange, HttpURLConnection.HTTP_INTERNAL_ERROR, "<html><body>An I/O error prevented the server from writing the image: " + e.getMessage() + "</body></html>"); return; } catch (FormatException e) { reportError(exchange, HttpURLConnection.HTTP_INTERNAL_ERROR, "<html><body>The imaging library failed to write the image because of a format error: " + e.getMessage() + "</body></html>"); return; } String html = String.format("<html><body>%s successfully written</body></html>", StringEscapeUtils.escapeHtml(outputFile.getAbsolutePath())); reportError(exchange, HttpURLConnection.HTTP_OK, html); } private byte[] convertImage(NDImage ndimage, PixelType pixelType, boolean toBigEndian) { double[] inputDouble = ndimage.getBuffer(); switch (pixelType) { case INT8: return convertToIntegerType(inputDouble, Byte.MIN_VALUE, Byte.MAX_VALUE, Byte.SIZE / 8, toBigEndian); case UINT8: return convertToIntegerType(inputDouble, 0, (1L << Byte.SIZE) - 1, Byte.SIZE / 8, toBigEndian); case INT16: return convertToIntegerType(inputDouble, Short.MIN_VALUE, Short.MAX_VALUE, Short.SIZE / 8, toBigEndian); case UINT16: return convertToIntegerType(inputDouble, 0, (1L << Short.SIZE) - 1, Short.SIZE / 8, toBigEndian); case INT32: return convertToIntegerType(inputDouble, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.SIZE / 8, toBigEndian); case UINT32: return convertToIntegerType(inputDouble, 0, (1L << Integer.SIZE) - 1, Integer.SIZE / 8, toBigEndian); case FLOAT: { int bpp = Float.SIZE / 8; byte[] buffer = new byte[inputDouble.length * bpp]; for (int i = 0; i < inputDouble.length; i++) { DataTools.unpackBytes(Float.floatToIntBits((float) inputDouble[i]), buffer, i * bpp, bpp, !toBigEndian); } return buffer; } case DOUBLE: { int bpp = Double.SIZE / 8; byte[] buffer = new byte[inputDouble.length * bpp]; for (int i = 0; i < inputDouble.length; i++) { DataTools.unpackBytes(Double.doubleToLongBits(inputDouble[i]), buffer, i * bpp, bpp, !toBigEndian); } return buffer; } default: throw new UnsupportedOperationException("The pixel type, " + pixelType.getValue() + ", should have been explicitly handled by the caller and an error should have been reported to the web client."); } } private byte[] convertToIntegerType(double[] buffer, long min, long max, int bpp, boolean toBigEndian) { byte[] outputData = new byte[buffer.length * bpp]; for (int i = 0; i < buffer.length; i++) { long value = (buffer[i] < min) ? min : (buffer[i] > max) ? max : (long) buffer[i]; DataTools.unpackBytes(value, outputData, i * bpp, bpp, !toBigEndian); } return outputData; } }