just before adding authorization service.
[photos] / src / main / java / org / wamblee / photos / model / authorization / AuthorizedAlbum.java
diff --git a/src/main/java/org/wamblee/photos/model/authorization/AuthorizedAlbum.java b/src/main/java/org/wamblee/photos/model/authorization/AuthorizedAlbum.java
new file mode 100644 (file)
index 0000000..7406d99
--- /dev/null
@@ -0,0 +1,233 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * 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.wamblee.photos.model.authorization;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+import org.wamblee.cache.Cache;
+import org.wamblee.cache.CachedObject;
+import org.wamblee.photos.model.Album;
+import org.wamblee.photos.model.Path;
+import org.wamblee.photos.model.Photo;
+import org.wamblee.photos.model.PhotoEntry;
+import org.wamblee.security.authorization.AllOperation;
+import org.wamblee.security.authorization.AuthorizationService;
+import org.wamblee.security.authorization.DeleteOperation;
+import org.wamblee.security.authorization.ReadOperation;
+import org.wamblee.security.authorization.WriteOperation;
+
+/**
+ * Decorator for an album providing defined behavior when used in a concurrent
+ * setting.
+ */
+public class AuthorizedAlbum extends AuthorizedPhotoEntry implements Album {
+
+    private static final Logger LOGGER = Logger
+            .getLogger(AuthorizedAlbum.class);
+
+    private AuthorizationService _authorizer;
+
+    private CachedObject<String, ArrayList<PhotoEntry>> _authorizedEntries;
+
+    private String _sessionId;
+
+    /**
+     * Constructs concurrent album as a decorator for an album implementation.
+     * 
+     * @param aAlbum
+     *            Album to decorate.
+     */
+    public AuthorizedAlbum(Album aAlbum, AuthorizationService aService,
+            Cache aCache, String aSessionId) {
+        super(aAlbum);
+        _authorizer = aService;
+        _authorizedEntries = new CachedObject<String, ArrayList<PhotoEntry>>(
+                aCache, aSessionId + "/" + aAlbum.getPath(),
+                new CachedObject.Computation<String, ArrayList<PhotoEntry>>() {
+                    public ArrayList<PhotoEntry> getObject(String aObjectKey) {
+                        return AuthorizedAlbum.this.compute();
+                    }
+                });
+        _sessionId = aSessionId;
+    }
+
+    /**
+     * Computes the cache of photo entries to which read access is allowed.
+     * 
+     * @return Photo entries to which read access is allowed.
+     */
+    private synchronized ArrayList<PhotoEntry> compute() {
+        LOGGER.info("Refreshing cache " + getPath());
+        ArrayList<PhotoEntry> result = new ArrayList<PhotoEntry>();
+        for (int i = 0; i < decorated().size(); i++) {
+            PhotoEntry entry = decorated().getEntry(i);
+            if (_authorizer.isAllowed(entry, new ReadOperation())) {
+                result.add(decorate(entry)); // subscription will take place
+                // automatically.
+            }
+        }
+        return result;
+    }
+
+    private Album decorated() {
+        return (Album) getEntry();
+    }
+
+    /**
+     * Creates a decorate for the photo entry to make it safe for concurrent
+     * access.
+     * 
+     * @param aEntry
+     *            Entry to decorate
+     * @return Decorated photo.
+     */
+    private <T extends PhotoEntry> T decorate(T aEntry) {
+        if (aEntry == null) {
+            return null;
+        } else if (aEntry instanceof Photo) {
+            return (T) new AuthorizedPhoto((Photo) aEntry);
+        } else if (aEntry instanceof Album) {
+            return (T) new AuthorizedAlbum((Album) aEntry, _authorizer,
+                    _authorizedEntries.getCache(), _sessionId);
+        } else {
+            throw new IllegalArgumentException(
+                    "Entry is neither a photo nor an album: " + aEntry);
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.photos.model.Album#getEntry(java.lang.String)
+     */
+    public PhotoEntry getEntry(String aPath) {
+        return getEntry(new Path(aPath));
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.photos.model.Album#getEntry(org.wamblee.photos.model.Path)
+     */
+    public PhotoEntry getEntry(Path aPath) {
+        if (aPath.isRoot()) {
+            return this;
+        }
+        List<PhotoEntry> cache = _authorizedEntries.get();
+        String id = aPath.getPart(0);
+        Path remainder = aPath.remainder();
+        for (PhotoEntry entry : cache) {
+            if (entry.getId().equals(id)) {
+                if (remainder.isRoot()) {
+                    return entry;
+                } else {
+                    if (!(entry instanceof Album)) {
+                        throw new IllegalArgumentException(getPath() + " "
+                                + aPath);
+                    }
+                    return ((Album) entry).getEntry(remainder);
+                }
+            }
+        }
+        return null;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.photos.model.Album#getEntry(int)
+     */
+    public PhotoEntry getEntry(int aIndex) {
+        return _authorizedEntries.get().get(aIndex);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.photos.model.Album#size()
+     */
+    public int size() {
+        return _authorizedEntries.get().size();
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.photos.model.Album#addImage(java.lang.String,
+     *      java.io.InputStream)
+     */
+    public void addImage(String aId, InputStream aImage) throws IOException {
+        _authorizer.check(this, new WriteOperation());
+        _authorizedEntries.invalidate();
+        decorated().addImage(aId, aImage);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.photos.model.Album#addAlbum(java.lang.String)
+     */
+    public void addAlbum(String aId) throws IOException {
+        _authorizer.check(this, new WriteOperation());
+        _authorizedEntries.invalidate();
+        decorated().addAlbum(aId);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.wamblee.photos.model.Album#removeEntry(java.lang.String)
+     */
+    public void removeEntry(String aId) throws IOException {
+        // Check whether deletion is allowed.
+        PhotoEntry entry = _authorizer.check(decorated().getEntry("/" + aId),
+                new DeleteOperation());
+        _authorizedEntries.invalidate();
+        decorated().removeEntry(aId);
+    }
+
+    public Photo findPhotoBefore(String aId) {
+        Photo entry = decorated().findPhotoBefore(aId);
+        while (entry != null
+                && !_authorizer.isAllowed(entry, new AllOperation())) {
+            entry = decorated().findPhotoBefore(entry.getId());
+        }
+        return decorate(entry);
+    }
+
+    public Photo findPhotoAfter(String aId) {
+        Photo entry = decorated().findPhotoAfter(aId);
+        while (entry != null
+                && !_authorizer.isAllowed(entry, new AllOperation())) {
+            entry = decorated().findPhotoAfter(entry.getId());
+        }
+        return decorate(entry);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        return "AuthorizedAlbum(path = '" + decorated().getPath() + "')";
+    }
+}