/** * Copyright 2009 Google Inc. * * 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 org.waveprotocol.wave.media.model; import org.waveprotocol.wave.model.adt.docbased.DocumentBasedBasicMap; import org.waveprotocol.wave.model.document.ObservableMutableDocument; import org.waveprotocol.wave.model.document.util.DefaultDocumentEventRouter; import org.waveprotocol.wave.model.util.Serializer; import org.waveprotocol.wave.model.util.Serializer.EnumSerializer; /** * Wraps a MutableDocument representing a set of client-side attachment metadata. No validation is * done to ensure that the document conforms to the expected schema, but certain operations may fail * if this is not the case. * <p/> * This class also accepts attribute update event notifications, and rebroadcasts them to a * listener. * <p/> * This class is threadsafe, providing the underlying MutableDocument and Node implementations are * thread-safe. * * @param <N> Node type * @param <E> Element node type * @param <T> Text node type */ public class AttachmentDocumentWrapper<N, E extends N, T extends N> implements MutableClientAttachment { private static class ImageImpl implements Image { private final int width; private final int height; public ImageImpl(int width, int height) { this.width = width; this.height = height; } @Override public int getHeight() { return height; } @Override public int getWidth() { return width; } } private static class ThumbnailImpl implements Thumbnail { private final int width; private final int height; public ThumbnailImpl(int width, int height) { this.width = width; this.height = height; } @Override public int getHeight() { return height; } @Override public int getWidth() { return width; } } /** * Represents a name of an entity in the document. */ interface Name { /** * Gets the name as used in the underlying XML. * * @return the name in string form */ String getName(); } /** * Tags and attribute names for the XML map keys. */ // VisibleForTesting enum KeyName { ATTACHMENT_SIZE, ATTACHMENT_URL, CREATOR, DOWNLOAD_TOKEN, FILENAME, IMAGE_HEIGHT, IMAGE_WIDTH, MALWARE, MIME_TYPE, STATUS, THUMBNAIL_HEIGHT, THUMBNAIL_WIDTH, THUMBNAIL_URL, UPLOAD_PROGRESS, UPLOAD_RETRIES; @Override public String toString() { return name().toLowerCase(); } } private final DocumentBasedBasicMap<E, KeyName, String> dataMap; private final ObservableMutableDocument<N, E, T> internalDocument; private volatile ImageImpl image = null; private volatile ThumbnailImpl thumbnail = null; /** * Factory method for creating AttachmentDocumentWrappers. * * @param document the CWM document to wrap * @return a new wrapper */ public static <N> AttachmentDocumentWrapper<N, ?, ?> create( ObservableMutableDocument<N, ?, ?> document) { return internalCreate(document); } /** * Private constructor. Use the factory method ({@link #create}) instead. * * @param document the document to wrap */ // VisibleForTesting AttachmentDocumentWrapper(ObservableMutableDocument<N, E, T> document) { internalDocument = document; dataMap = DocumentBasedBasicMap.create(DefaultDocumentEventRouter.create(document), document.getDocumentElement(), new KeyNameSerializer(), Serializer.STRING, "node", "key", "value"); } @Override public String getAttachmentUrl() { return dataMap.get(KeyName.ATTACHMENT_URL); } @Override public String getCreator() { return dataMap.get(KeyName.CREATOR); } /** Only @VisibleForTesting. */ ObservableMutableDocument<N, E, T> getDocument() { return internalDocument; } @Override public String getFilename() { return dataMap.get(KeyName.FILENAME); } @Override public ImageImpl getImage() { if (image == null) { Integer width = getAsInt(KeyName.IMAGE_WIDTH); Integer height = getAsInt(KeyName.IMAGE_HEIGHT); if (width != null && height != null) { image = new ImageImpl(width, height); } } return image; } @Override public String getMimeType() { return dataMap.get(KeyName.MIME_TYPE); } @Override public Long getSize() { return getAsLong(KeyName.ATTACHMENT_SIZE); } @Override public ThumbnailImpl getThumbnail() { if (thumbnail == null) { Integer width = getAsInt(KeyName.THUMBNAIL_WIDTH); Integer height = getAsInt(KeyName.THUMBNAIL_HEIGHT); if (width != null && height != null) { thumbnail = new ThumbnailImpl(width, height); } } return thumbnail; } @Override public String getThumbnailUrl() { return dataMap.get(KeyName.THUMBNAIL_URL); } @Override public long getUploadedByteCount() { return getAsLong(KeyName.UPLOAD_PROGRESS); } @Override public long getUploadRetryCount() { Long retryCount = getAsLong(KeyName.UPLOAD_RETRIES); return retryCount == null ? 0 : retryCount; } @Override public boolean isMalware() { Boolean b = getAsBoolean(KeyName.MALWARE); return b != null && b; } @Override public String getStatus() { return dataMap.get(KeyName.STATUS); } @Override public void setAttachmentUrl(String url) { dataMap.put(KeyName.ATTACHMENT_URL, url); } @Override public void setCreator(String userId) { dataMap.put(KeyName.CREATOR, userId); } @Override public void setDownloadToken(String token) { dataMap.put(KeyName.DOWNLOAD_TOKEN, token); } @Override public void setFilename(String filename) { dataMap.put(KeyName.FILENAME, filename); } @Override public Image setImage(int width, int height) { dataMap.put(KeyName.IMAGE_WIDTH, Integer.toString(width)); dataMap.put(KeyName.IMAGE_HEIGHT, Integer.toString(height)); image = new ImageImpl(width, height); return image; } @Override public void setMalware(Boolean malware) { setBooleanAttribute(KeyName.MALWARE, malware); } @Override public void setMimeType(String mimeType) { dataMap.put(KeyName.MIME_TYPE, mimeType); } @Override public void setSize(Long size) { setLongAttribute(KeyName.ATTACHMENT_SIZE, size); } /** * TODO(user): Correlate all of the various upload / attachment states * throughout the code base. */ @Override public void setStatus(String status) { dataMap.put(KeyName.STATUS, status); } @Override public Thumbnail setThumbnail(int width, int height) { dataMap.put(KeyName.THUMBNAIL_WIDTH, Integer.toString(width)); dataMap.put(KeyName.THUMBNAIL_HEIGHT, Integer.toString(height)); thumbnail = new ThumbnailImpl(width, height); return thumbnail; } @Override public void setThumbnailUrl(String url) { dataMap.put(KeyName.THUMBNAIL_URL, url); } @Override public void setUploadedByteCount(long uploadProgress) { setLongAttribute(KeyName.UPLOAD_PROGRESS, uploadProgress); } @Override public void setUploadRetryCount(long retryCount) { setLongAttribute(KeyName.UPLOAD_RETRIES, retryCount); } /** * Convenience method to increment the retry count. * * @return the retry count after being incremented. */ public long incrementRetryCount() { long retryCount = getUploadRetryCount() + 1; setUploadRetryCount(retryCount); return retryCount; } private Boolean getAsBoolean(KeyName key) { String value = dataMap.get(key); return value != null ? Boolean.parseBoolean(value) : null; } private Integer getAsInt(KeyName key) { String value = dataMap.get(key); return value != null ? Integer.parseInt(value) : null; } private Long getAsLong(KeyName key) { String value = dataMap.get(key); return value != null ? Long.parseLong(value) : null; } /** * Internal factory method implementation. Required because javac is not smart enough to * work out the generics if we inline this call. * * @param document the CWM document to wrap * @return a new wrapper */ private static <N, E extends N, T extends N> AttachmentDocumentWrapper<N, ?, ?> internalCreate( ObservableMutableDocument<N, E, T> document) { return new AttachmentDocumentWrapper<N, E, T>(document); } private void setBooleanAttribute(KeyName key, boolean value) { dataMap.put(key, Boolean.toString(value)); } private void setLongAttribute(KeyName key, long value) { dataMap.put(key, Long.toString(value)); } private static class KeyNameSerializer extends EnumSerializer<KeyName> { public KeyNameSerializer() { super(KeyName.class); } @Override public String toString(KeyName keyName) { return super.toString(keyName).toLowerCase(); } @Override public KeyName fromString(String s) { return super.fromString(s.toUpperCase()); } } }