/** * 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.waveprotocol.box.server.attachment; import com.google.common.base.Preconditions; import com.google.inject.Inject; import java.awt.Color; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URLConnection; import java.util.logging.Level; import java.util.logging.Logger; import javax.imageio.ImageIO; import org.waveprotocol.box.attachment.AttachmentMetadata; import org.waveprotocol.box.attachment.ImageMetadata; import org.waveprotocol.box.attachment.impl.AttachmentMetadataImpl; import org.waveprotocol.box.attachment.impl.ImageMetadataImpl; import org.waveprotocol.box.server.persistence.AttachmentStore; import org.waveprotocol.box.server.persistence.AttachmentStore.AttachmentData; import org.waveprotocol.box.server.rpc.AttachmentServlet; import org.waveprotocol.wave.media.model.AttachmentId; import org.waveprotocol.wave.model.id.WaveletName; import org.waveprotocol.wave.model.wave.ParticipantId; import org.waveprotocol.wave.model.waveref.WaveRef; import org.waveprotocol.wave.util.escapers.jvm.JavaWaverefEncoder; /** * Serves storing and getting of attachments. * * @author akaplanov@gmail.com (A. Kaplanov) */ public class AttachmentService { private static final Logger LOG = Logger.getLogger(AttachmentService.class.getName()); public static final String THUMBNAIL_MIME_TYPE = "image/jpeg"; public static final String THUMBNAIL_FORMAT_NAME = "jpeg"; public static final int THUMBNAIL_PATTERN_WIDTH = 95; public static final int THUMBNAIL_PATTERN_HEIGHT = 60; private static final int MAX_THUMBNAIL_WIDTH = 200; private static final int MAX_THUMBNAIL_HEIGHT = 200; private final AttachmentStore store; @Inject private AttachmentService(AttachmentStore store) { this.store = store; } public AttachmentMetadata getMetadata(AttachmentId attachmentId) throws IOException { return store.getMetadata(attachmentId); } public AttachmentData getAttachment(AttachmentId attachmentId) throws IOException { return store.getAttachment(attachmentId); } public AttachmentData getThumbnail(AttachmentId attachmentId) throws IOException { return store.getThumbnail(attachmentId); } public void storeAttachment(AttachmentId attachmentId, InputStream in, WaveletName waveletName, String fileName, ParticipantId creator) throws IOException { store.storeAttachment(attachmentId, in); buildAndStoreMetadataWithThumbnail(attachmentId, waveletName, fileName, creator); } public AttachmentMetadata buildAndStoreMetadataWithThumbnail(AttachmentId attachmentId, WaveletName waveletName, String fileName, ParticipantId creator) throws IOException { AttachmentData data = store.getAttachment(attachmentId); if (data == null) { throw new IOException("No such atachment " + attachmentId.serialise()); } AttachmentMetadata metadata = new AttachmentMetadataImpl(); metadata.setAttachmentId(attachmentId.serialise()); metadata.setAttachmentUrl(AttachmentServlet.ATTACHMENT_URL + "/" + attachmentId.serialise()); metadata.setThumbnailUrl(AttachmentServlet.THUMBNAIL_URL + "/" + attachmentId.serialise()); metadata.setWaveRef(waveletName2WaveRef(waveletName)); metadata.setFileName(fileName); String contentType = getMimeType(fileName); metadata.setMimeType(contentType); metadata.setSize(data.getSize()); metadata.setCreator((creator != null) ? creator.getAddress() : ""); BufferedImage image = null; try { image = ImageIO.read(data.getInputStream()); } catch (IOException ex) { LOG.log(Level.SEVERE, "Identifying attachment", ex); } if (image != null) { ImageMetadata imageMetadata = new ImageMetadataImpl(); imageMetadata.setWidth(image.getWidth()); imageMetadata.setHeight(image.getHeight()); metadata.setImageMetadata(imageMetadata); try { BufferedImage thumbnail = makeThumbnail(image); storeThumbnail(attachmentId, thumbnail); ImageMetadata thumbnailMetadata = new ImageMetadataImpl(); thumbnailMetadata.setWidth(thumbnail.getWidth()); thumbnailMetadata.setHeight(thumbnail.getHeight()); metadata.setThumbnailMetadata(thumbnailMetadata); } catch (IOException ex) { LOG.log(Level.SEVERE, "Building attachment thumbnail", ex); } } else { ImageMetadata thumbnailMetadata = new ImageMetadataImpl(); thumbnailMetadata.setWidth(THUMBNAIL_PATTERN_WIDTH); thumbnailMetadata.setHeight(THUMBNAIL_PATTERN_HEIGHT); metadata.setThumbnailMetadata(thumbnailMetadata); } store.storeMetadata(attachmentId, metadata); return metadata; } private static BufferedImage makeThumbnail(BufferedImage image) { int imageWidth = image.getWidth(); int imageHeight = image.getHeight(); Preconditions.checkState(imageHeight != 0); Preconditions.checkState(imageWidth != 0); int thumbnailWidth = imageWidth < MAX_THUMBNAIL_WIDTH ? imageWidth : MAX_THUMBNAIL_WIDTH; int thumbnailHeight = imageHeight < MAX_THUMBNAIL_HEIGHT ? imageHeight : MAX_THUMBNAIL_HEIGHT; if (imageWidth * thumbnailHeight < imageHeight * thumbnailWidth) { thumbnailWidth = imageWidth * thumbnailHeight / imageHeight; } else { thumbnailHeight = imageHeight * thumbnailWidth / imageWidth; } BufferedImage thumbnail = new BufferedImage(thumbnailWidth, thumbnailHeight, BufferedImage.TYPE_INT_RGB); Graphics2D g = thumbnail.createGraphics(); try { g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); g.setBackground(Color.BLACK); g.clearRect(0, 0, thumbnailWidth, thumbnailHeight); g.drawImage(image, 0, 0, thumbnailWidth, thumbnailHeight, null); } finally { g.dispose(); } return thumbnail; } private void storeThumbnail(AttachmentId attachemntId, BufferedImage thumbnail) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); ImageIO.write(thumbnail, THUMBNAIL_FORMAT_NAME, out); store.storeThumbnail(attachemntId, new ByteArrayInputStream(out.toByteArray())); } private static String waveletName2WaveRef(WaveletName waveletName) { WaveRef waveRef = WaveRef.of(waveletName.waveId, waveletName.waveletId); return JavaWaverefEncoder.encodeToUriPathSegment(waveRef); } private static String getMimeType(String fileName) { String mimeType = URLConnection.getFileNameMap().getContentTypeFor(fileName); if (mimeType == null) { return "application/octet-stream"; } return mimeType; } }