--- /dev/null
+ // https://blogs.oracle.com/monzillo/entry/using_jacc_to_determine_a
+
+ Subject subject = (Subject) PolicyContext
+ .getContext("javax.security.auth.Subject.container");
+ CodeSource cs = new CodeSource(null,
+ (java.security.cert.Certificate[]) null);
+ Principal principals[] = (subject == null ? new Principal[0] : subject
+ .getPrincipals().toArray(new Principal[0]));
+ for (Principal principal : principals) {
+ System.out
+ .println(principal + " " + principal.getClass().getName());
+ }
+
+ ProtectionDomain pd = new ProtectionDomain(cs, null, null, principals);
+ Policy policy = Policy.getPolicy();
+ PermissionCollection pc = policy.getPermissions(pd);
+ pc.implies(new WebRoleRefPermission(null, null));
+ Set<String> roleSet = new HashSet<String>();
+ Enumeration<Permission> e = pc.elements();
+ while (e.hasMoreElements()) {
+ Permission p = e.nextElement();
+ if (p instanceof WebRoleRefPermission) {
+ String roleRef = p.getActions();
+ // confirm roleRef via isUserInRole to ensure proper scoping to
+ // Servlet Name
+ // if (request.isUserInRole(roleRef)) {
+ roleSet.add(p.getActions());
+ System.out.println("Role actions: " + p.getActions());
+ System.out.println("Role name: " + p.getName());
+ // }
+ }
+ }
+ System.out.println("Roles in the context of this page: " + roleSet);
-janse
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.wamblee.photos</groupId>
+ <artifactId>wamblee-photos</artifactId>
+ <packaging>war</packaging>
+ <version>0.1-SNAPSHOT</version>
+ <name>photos</name>
+ <description>
+ Photos application.
+ </description>
+ <properties>
+ <wicket.version>1.4.9</wicket.version>
+ <jetty.version>6.1.4</jetty.version>
+ <utils.version>0.6</utils.version>
+ </properties>
+
+ <!--
+ TODO <organization> <name>company name</name> <url>company url</url>
+ </organization>
+ -->
+ <url>http://wamblee.org</url>
+ <licenses>
+ <license>
+ <name>The Apache Software License, Version 2.0</name>
+ <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+ <distribution>repo</distribution>
+ </license>
+ </licenses>
+ <scm>
+ <url>http://wamblee.org/gitweb/photos</url>
+ <connection>scm:git:https://wamblee.org/git/public/photos</connection>
+ <developerConnection>scm:git:https://wamblee.org/git/public/photos</developerConnection>
+ </scm>
+ <developers>
+ <developer>
+ <email>erik@wamblee.org</email>
+ <name>Erik Brakkee</name>
+ <url>http://brakkee.org</url>
+ </developer>
+ </developers>
+
+ <dependencies>
+
+ <dependency>
+ <groupId>javax.portlet</groupId>
+ <artifactId>portlet-api</artifactId>
+ <version>2.0</version>
+ </dependency>
+
+
+ <dependency>
+ <groupId>dom4j</groupId>
+ <artifactId>dom4j</artifactId>
+ <version>1.6</version>
+ <exclusions>
+ <exclusion>
+ <groupId>xml-apis</groupId>
+ <artifactId>xml-apis</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>jaxen</groupId>
+ <artifactId>jaxen</artifactId>
+ <version>1.1-beta-9</version>
+ <exclusions>
+ <exclusion>
+ <groupId>xom</groupId>
+ <artifactId>xom</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>xerces</groupId>
+ <artifactId>xmlParserAPIs</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+
+
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-wicket-joe</artifactId>
+ <version>${utils.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-wicket-components</artifactId>
+ <version>${utils.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-support-cdi</artifactId>
+ <version>${utils.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-support-cdi</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ <version>${utils.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-security-usermgt</artifactId>
+ <version>${utils.version}</version>
+ </dependency>
+
+
+ <!-- WICKET DEPENDENCIES -->
+ <dependency>
+ <groupId>org.apache.wicket</groupId>
+ <artifactId>wicket</artifactId>
+ <version>${wicket.version}</version>
+ </dependency>
+ <!--
+ OPTIONAL <dependency> <groupId>org.apache.wicket</groupId>
+ <artifactId>wicket-extensions</artifactId>
+ <version>${wicket.version}</version> </dependency>
+ -->
+
+ <!-- LOGGING DEPENDENCIES - LOG4J -->
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-log4j12</artifactId>
+ <version>1.4.2</version>
+ </dependency>
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <version>1.2.14</version>
+ </dependency>
+
+ <!-- JUNIT DEPENDENCY FOR TESTING -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.4</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <version>1.8.5</version>
+ <scope>test</scope>
+ </dependency>
+
+
+ <!-- JETTY DEPENDENCIES FOR TESTING -->
+ <dependency>
+ <groupId>org.mortbay.jetty</groupId>
+ <artifactId>jetty</artifactId>
+ <version>${jetty.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mortbay.jetty</groupId>
+ <artifactId>jetty-util</artifactId>
+ <version>${jetty.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mortbay.jetty</groupId>
+ <artifactId>jetty-management</artifactId>
+ <version>${jetty.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <!-- JPA testing -->
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-test-enterprise</artifactId>
+ <version>${utils.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-test-eclipselink</artifactId>
+ <version>${utils.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.persistence</groupId>
+ <artifactId>javax.persistence</artifactId>
+ <version>2.0.0</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.eclipse.persistence</groupId>
+ <artifactId>eclipselink</artifactId>
+ <version>2.0.2</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>javax</groupId>
+ <artifactId>javaee-api</artifactId>
+ <version>6.0</version>
+ <scope>provided</scope>
+ </dependency>
+
+
+ </dependencies>
+ <build>
+ <resources>
+ <resource>
+ <filtering>false</filtering>
+ <directory>src/main/resources</directory>
+ </resource>
+ <resource>
+ <filtering>false</filtering>
+ <directory>src/main/java</directory>
+ <includes>
+ <include>**</include>
+ </includes>
+ <excludes>
+ <exclude>**/*.java</exclude>
+ </excludes>
+ </resource>
+ </resources>
+ <testResources>
+ <testResource>
+ <filtering>false</filtering>
+ <directory>src/test/java</directory>
+ <includes>
+ <include>**</include>
+ </includes>
+ <excludes>
+ <exclude>**/*.java</exclude>
+ </excludes>
+ </testResource>
+ </testResources>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-war-plugin</artifactId>
+ <version>2.0</version>
+ <configuration> </configuration>
+ </plugin>
+
+
+ <plugin>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>copy-dependencies</id>
+ <phase>package</phase>
+ <goals>
+ <goal>copy-dependencies</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <inherited>true</inherited>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ <optimize>true</optimize>
+ <debug>true</debug>
+ <compilerArgument>-XDignore.symbol.file</compilerArgument>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.mortbay.jetty</groupId>
+ <artifactId>maven-jetty-plugin</artifactId>
+ </plugin>
+
+ </plugins>
+ </build>
+
+
+ <repositories>
+ <repository>
+ <id>EclipseLink Repo</id>
+ <url>http://www.eclipse.org/downloads/download.php?r=1&nf=1&file=/rt/eclipselink/maven.repo</url>
+ </repository>
+ </repositories>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <activation>
+ <property>
+ <name>performRelease</name>
+ <value>true</value>
+ </property>
+ </activation>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-release-plugin</artifactId>
+ <version>2.0</version>
+ <configuration>
+ <autoVersionSubmodules>true</autoVersionSubmodules>
+ <goals>javadoc:jar deploy</goals>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-gpg-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>sign-artifacts</id>
+ <phase>verify</phase>
+ <goals>
+ <goal>sign</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+ <distributionManagement>
+ <repository>
+ <id>sonatype-nexus-staging</id>
+ <name>Nexus Release Repository</name>
+ <url>http://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
+ </repository>
+ </distributionManagement>
+
+</project>
--- /dev/null
+<ehcache>
+
+ <!-- Sets the path to the directory where cache .data files are created.
+
+ If the path is a Java System Property it is replaced by
+ its value in the running VM.
+
+ The following properties are translated:
+ user.home - User's home directory
+ user.dir - User's current working directory
+ java.io.tmpdir - Default temp file path -->
+ <diskStore path="java.io.tmpdir"/>
+
+
+ <!--Default Cache configuration. These will applied to caches programmatically created through
+ the CacheManager.
+
+ The following attributes are required:
+
+ maxElementsInMemory - Sets the maximum number of objects that will be created in memory
+ eternal - Sets whether elements are eternal. If eternal, timeouts are ignored and the
+ element is never expired.
+ overflowToDisk - Sets whether elements can overflow to disk when the in-memory cache
+ has reached the maxInMemory limit.
+
+ The following attributes are optional:
+ timeToIdleSeconds - Sets the time to idle for an element before it expires.
+ i.e. The maximum amount of time between accesses before an element expires
+ Is only used if the element is not eternal.
+ Optional attribute. A value of 0 means that an Element can idle for infinity.
+ The default value is 0.
+ timeToLiveSeconds - Sets the time to live for an element before it expires.
+ i.e. The maximum time between creation time and when an element expires.
+ Is only used if the element is not eternal.
+ Optional attribute. A value of 0 means that and Element can live for infinity.
+ The default value is 0.
+ diskPersistent - Whether the disk store persists between restarts of the Virtual Machine.
+ The default value is false.
+ diskExpiryThreadIntervalSeconds- The number of seconds between runs of the disk expiry thread. The default value
+ is 120 seconds.
+ -->
+
+ <defaultCache
+ maxElementsInMemory="10000"
+ eternal="false"
+ overflowToDisk="false"
+ timeToIdleSeconds="120"
+ timeToLiveSeconds="120"
+ diskPersistent="false"
+ diskExpiryThreadIntervalSeconds="120"
+ />
+
+ <cache
+ name="users"
+ maxElementsInMemory="10000"
+ eternal="false"
+ overflowToDisk="false"
+ timeToIdleSeconds="120"
+ timeToLiveSeconds="120"
+ diskPersistent="false"
+ diskExpiryThreadIntervalSeconds="120"
+ />
+
+ <cache
+ name="photos"
+ maxElementsInMemory="1000"
+ eternal="false"
+ overflowToDisk="false"
+ timeToIdleSeconds="120"
+ timeToLiveSeconds="120"
+ diskPersistent="false"
+ diskExpiryThreadIntervalSeconds="120"
+ />
+
+</ehcache>
--- /dev/null
+
+##############################################################################
+# Full path name of the physical storage of the photo albums.
+##############################################################################
+org.wamblee.photos.path=/home/erik/java/workspace/album
+
--- /dev/null
+<persistence xmlns="http://java.sun.com/xml/ns/persistence"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
+ version="2.0">
+
+ <persistence-unit name="photos" transaction-type="JTA">
+ <jta-data-source>jdbc/PhotoXChange</jta-data-source>
+
+ <!-- from the utiliities library -->
+ <class>org.wamblee.security.authentication.User</class>
+ <class>org.wamblee.security.authentication.Group</class>
+
+ <!--
+ <class>org.wamblee.security.authorization.AbstractUserCondition</class>
+ <class>org.wamblee.security.authorization.AnyUserCondition</class>
+ <class>org.wamblee.security.authorization.GroupUserCondition</class>
+ <class>org.wamblee.security.authorization.AbstractOperationCondition</class>
+ <class>org.wamblee.security.authorization.IsaOperationCondition</class>
+ <class>org.wamblee.security.authorization.AbstractPathCondition</class>
+ <class>org.wamblee.security.authorization.RegexpPathCondition</class>
+ <class>org.wamblee.security.authorization.StartsWithPathCondition</class>
+
+ <class>org.wamblee.security.authorization.AbstractAuthorizationRule</class>
+
+
+ <class>org.wamblee.security.authorization.AbstractAuthorizationService</class>
+ <class>org.wamblee.security.authorization.DefaultAuthorizationService</class>
+ -->
+
+ <!-- from the photo app, subclass of UrlAuthorizationRule -->
+
+ <!--
+ <class>org.wamblee.photos.authorizationrules.PhotoAuthorizationRule</class>
+ -->
+
+ <exclude-unlisted-classes>true</exclude-unlisted-classes>
+
+ </persistence-unit>
+</persistence>
\ No newline at end of file
--- /dev/null
+org.wamblee.photos.model.plumbing.EagerExtension
--- /dev/null
+/*
+ * 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.concurrent;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.wamblee.photos.model.Album;
+import org.wamblee.photos.model.Path;
+import org.wamblee.photos.model.Photo;
+import org.wamblee.photos.model.PhotoEntry;
+
+/**
+ * Decorator for an album providing defined behavior when used in a concurrent setting.
+ */
+public class ConcurrentAlbum extends ConcurrentPhotoEntry implements Album {
+
+ /**
+ * Constructs concurrent album as a decorator for an album implementation.
+ * @param aAlbum Album to decorate.
+ */
+ public ConcurrentAlbum(Album aAlbum) {
+ super(aAlbum);
+ }
+
+ 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 ConcurrentPhoto((Photo)aEntry);
+ } else if ( aEntry instanceof Album) {
+ return (T) new ConcurrentAlbum((Album)aEntry);
+ } 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) {
+ lock().acquireRead();
+ try {
+ return decorate(decorated().getEntry(aPath));
+ } finally {
+ lock().releaseRead();
+ }
+ }
+
+ public PhotoEntry getEntry(Path aPath) {
+ lock().acquireRead();
+ try {
+ return decorate(decorated().getEntry(aPath));
+ } finally {
+ lock().releaseRead();
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.photos.model.Album#getEntry(int)
+ */
+ public PhotoEntry getEntry(int aIndex) {
+ lock().acquireRead();
+ try {
+ return decorate(decorated().getEntry(aIndex));
+ } finally {
+ lock().releaseRead();
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.photos.model.Album#size()
+ */
+ public int size() {
+ lock().acquireRead();
+ try {
+ return decorated().size();
+ } finally {
+ lock().releaseRead();
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.photos.model.Album#addImage(java.lang.String, java.io.InputStream)
+ */
+ public void addImage(String aId, InputStream aImage) throws IOException {
+ lock().acquireWrite();
+ try {
+ decorated().addImage(aId, aImage);
+ } finally {
+ lock().releaseWrite();
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.photos.model.Album#addAlbum(java.lang.String)
+ */
+ public void addAlbum(String aId) throws IOException {
+ lock().acquireWrite();
+ try {
+ decorated().addAlbum(aId);
+ } finally {
+ lock().releaseWrite();
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.photos.model.Album#removeEntry(java.lang.String)
+ */
+ public void removeEntry(String aId) throws IOException {
+ lock().acquireWrite();
+ try {
+ decorated().removeEntry(aId);
+ } finally {
+ lock().releaseWrite();
+ }
+ }
+
+ public Photo findPhotoBefore(String aId) {
+ lock().acquireWrite();
+ try {
+ return decorate(decorated().findPhotoBefore(aId));
+ } finally {
+ lock().releaseWrite();
+ }
+ }
+
+ public Photo findPhotoAfter(String aId) {
+ lock().acquireWrite();
+ try {
+ return decorate(decorated().findPhotoAfter(aId));
+ } finally {
+ lock().releaseWrite();
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.concurrent;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.wamblee.photos.model.Photo;
+
+/**
+ * Decorator for a photo to make it thread-safe.
+ */
+public class ConcurrentPhoto extends ConcurrentPhotoEntry implements Photo {
+
+ public ConcurrentPhoto(Photo aPhoto) {
+ super(aPhoto);
+ }
+
+ private Photo decorated() {
+ return (Photo)getEntry();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.photos.model.Photo#getThumbNail()
+ */
+ public InputStream getThumbNail() throws IOException {
+ lock().acquireRead();
+ try {
+ return decorated().getThumbNail();
+ } finally {
+ lock().releaseRead();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.photos.model.Photo#getPhoto()
+ */
+ public InputStream getPhoto() throws IOException {
+ lock().acquireRead();
+ try {
+ return decorated().getPhoto();
+ } finally {
+ lock().releaseRead();
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.concurrent;
+
+import org.wamblee.concurrency.ReadWriteLock;
+import org.wamblee.photos.model.PhotoEntry;
+
+/**
+ * Decorator base class for a photo entry.
+ */
+public class ConcurrentPhotoEntry implements PhotoEntry {
+
+ private PhotoEntry _entry;
+
+ private ReadWriteLock _lock;
+
+ protected ConcurrentPhotoEntry(PhotoEntry aEntry) {
+ _entry = aEntry;
+ _lock = new ReadWriteLock();
+ }
+
+ protected PhotoEntry getEntry() {
+ return _entry;
+ }
+
+ protected ReadWriteLock lock() {
+ return _lock;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.photos.model.PhotoEntry#getId()
+ */
+ public String getId() {
+ _lock.acquireRead();
+ try {
+ return _entry.getId();
+ } finally {
+ _lock.releaseRead();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.photos.model.PhotoEntry#getPath()
+ */
+ public String getPath() {
+ _lock.acquireRead();
+ try {
+ return _entry.getPath();
+ } finally {
+ _lock.releaseRead();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Comparable#compareTo(java.lang.Object)
+ */
+ public int compareTo(PhotoEntry aEntry) {
+ _lock.acquireRead();
+ try {
+ return _entry.compareTo(aEntry); // TODO: is this safe?
+ }
+ finally {
+ _lock.releaseRead();
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+
+/**
+ * Represents a collection of photo albums for one speci
+ */
+public interface Album extends PhotoEntry {
+
+ /**
+ * Returns a photo entry with the given path.
+ * A path consists of a number of ids of photo entries
+ * separated by a forward slash. The path should wtart with
+ * a forward slash. The path is relative to the current
+ * album.
+ * @param aPath Photo entry path.
+ * @return PhotoEntry if found or null otherwise.
+ */
+ PhotoEntry getEntry(String aPath);
+
+ /**
+ * Returns a photo entry with the given path.
+ * A path consists of a number of ids of photo entries
+ * separated by a forward slash. The path should wtart with
+ * a forward slash. The path is relative to the current
+ * album.
+ * @param aPath Photo entry path.
+ * @return PhotoEntry if found or null otherwise.
+ */
+ PhotoEntry getEntry(Path aPath);
+
+
+ /**
+ * Returns the photo entry with the given index.
+ * 0 <= index < size()
+ * @param aIndex Index of the photo.
+ * @return Photo at given index.
+ */
+ PhotoEntry getEntry(int aIndex);
+
+ /**
+ * Returns the number of entries in the album.
+ * @return Number of entries.
+ */
+ int size();
+
+ /**
+ * Adds an image to the album with the given id.
+ * @param aId Id of the image (excluding file extension).
+ * @param aImage Image.
+ * @throws IOException In case the image cannot be added.
+ */
+ void addImage(String aId, InputStream aImage) throws IOException;
+
+ /**
+ * Adds a new album if this is allowed and no album with the
+ * given
+ * @param aId Album id.
+ * @throws IOException In case the album cannot be added.
+ */
+ void addAlbum(String aId) throws IOException;
+
+ /**
+ * Removes the given entry if it can be removed.
+ * @param aId Entry id.
+ * @throws IOException In case the entry cannot be removed.
+ */
+ void removeEntry(String aId) throws IOException;
+
+ /**
+ * Gets the first photo before the entry with the given id.
+ * @param aId Id of the given entry.
+ * @return Photo or null if no such photo exists.
+ */
+ Photo findPhotoBefore(String aId);
+
+ /**
+ * Gets the first photo after a given entry.
+ * @param aId Id of the given entry.
+ * @return Photo or null if no such photo exists.
+ */
+ Photo findPhotoAfter(String aId);
+}
--- /dev/null
+/*
+ * 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;
+
+import java.io.Serializable;
+
+/**
+ * Represents a path to a photo.
+ *
+ */
+public class Path implements Serializable {
+
+ static final long serialVersionUID = 1287597039089552731L;
+
+ /**
+ * The parts of the path.
+ */
+ private String[] _path;
+
+ /**
+ * Constructs an empty path.
+ *
+ */
+ public Path() {
+ _path = new String[0];
+ }
+
+ /**
+ * Construcst the path based on a path string.
+ * @param aPath Path, must start with a /
+ */
+ public Path(String aPath) {
+ if ( aPath == null ) {
+ throw new IllegalArgumentException("path is null");
+ }
+ if ( !aPath.startsWith("/")) {
+ throw new IllegalArgumentException("path must start with / '" + aPath + "'");
+ }
+ if ( aPath.length() == 1 ) {
+ _path = new String[0];
+ return;
+ }
+ _path = aPath.substring(1).split("/");
+ }
+
+ /**
+ * Constructs the path based on an array of the parts.
+ * @param aPath Path.
+ */
+ private Path(String[] aPath) {
+ _path = aPath;
+ }
+
+ /**
+ * Returns the child of a path by appending a part.
+ * @param aId Id to append.
+ * @return Child.
+ * @throws IllegalArgumentException In case the id contains a /
+ */
+ public Path child(String aId) {
+ if ( aId.matches("/")) {
+ throw new IllegalArgumentException("Id '" + aId + "' contains a /");
+ }
+ String[] newpath = new String[_path.length + 1];
+ System.arraycopy(_path, 0, newpath, 0, _path.length);
+ newpath[_path.length] = aId;
+ return new Path(newpath);
+ }
+
+ /**
+ * Checks if the path is the root path.
+ * @return True if the path is the root.
+ */
+ public boolean isRoot() {
+ return _path.length == 0;
+ }
+
+ /**
+ * Gets the parent of the path.
+ * @return Parent of the path or the root if the path is already at the root.
+ */
+ public Path parent() {
+ if ( isRoot() ) {
+ return this;
+ }
+ String[] newpath = new String[_path.length -1];
+ System.arraycopy(_path, 0, newpath, 0, _path.length - 1);
+ return new Path(newpath);
+ }
+
+ /**
+ * Converts the path to a regular path.
+ * @return A path, always starting with a /
+ */
+ public String toString() {
+ if ( _path.length == 0 ) {
+ return "/";
+ }
+ String result = "";
+ for (String part: _path) {
+ result += "/" + part;
+ }
+ return result;
+ }
+
+ /**
+ * Gets the id (last part) of the path.
+ * @return Id or null if the path has no parts (root)
+ */
+ public String getId() {
+ if ( _path.length == 0 ) {
+ return null;
+ }
+ return _path[_path.length -1];
+ }
+
+ /**
+ * Returns the number of components in the path.
+ * @return Size.
+ */
+ public int size() {
+ return _path.length;
+ }
+
+ /**
+ * Returns a part of the path.
+ * @param aPart Part number (starts at 0).
+ * @return Part.
+ */
+ public String getPart(int aPart) {
+ return _path[aPart];
+ }
+
+ /**
+ * Strips the first part of the path and returns the remainder.
+ * @return Remainder.
+ */
+ public Path remainder() {
+ if ( _path.length == 0 ) {
+ return null;
+ }
+ String[] result = new String[_path.length-1];
+ for (int i = 0; i < _path.length-1; i++) {
+ result[i] = _path[i+1];
+ }
+ return new Path(result);
+ }
+}
--- /dev/null
+/*
+ * 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;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Represents a single photo.
+ */
+public interface Photo extends PhotoEntry {
+
+ /**
+ * Returns an opened input stream to the thumbnail JPEG
+ * picture for the given photo.
+ * @return Input stream.
+ */
+ InputStream getThumbNail() throws IOException;
+
+ /**
+ * Returns an opened input stream to the full JPEG
+ * picture.
+ * @return Input stream.
+ */
+ InputStream getPhoto() throws IOException;
+}
--- /dev/null
+/*
+ * 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;
+
+/**
+ * Represents an entry inside a photo album.
+ */
+public interface PhotoEntry extends Comparable<PhotoEntry> {
+
+ /**
+ * Photo entry id.
+ * @return Id of the entry.
+ */
+ String getId();
+
+ /**
+ * Gets the path of the given photo with respect to the
+ * root album. The path will start with a "/".
+ */
+ String getPath();
+}
--- /dev/null
+/*
+ * 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() + "')";
+ }
+}
--- /dev/null
+/*
+ * 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 org.wamblee.photos.model.Photo;
+
+/**
+ * Decorator for a photo to make it thread-safe.
+ */
+public class AuthorizedPhoto extends AuthorizedPhotoEntry implements Photo {
+
+ public AuthorizedPhoto(Photo aPhoto) {
+ super(aPhoto);
+ }
+
+ private Photo decorated() {
+ return (Photo) getEntry();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.photos.model.Photo#getThumbNail()
+ */
+ public InputStream getThumbNail() throws IOException {
+ return decorated().getThumbNail();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.photos.model.Photo#getPhoto()
+ */
+ public InputStream getPhoto() throws IOException {
+ return decorated().getPhoto();
+ }
+}
--- /dev/null
+/*
+ * 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 org.wamblee.photos.model.PhotoEntry;
+
+/**
+ * Decorator base class for a photo entry.
+ */
+public class AuthorizedPhotoEntry implements PhotoEntry {
+
+ private PhotoEntry _entry;
+
+ protected AuthorizedPhotoEntry(PhotoEntry aEntry) {
+ _entry = aEntry;
+ }
+
+ protected PhotoEntry getEntry() {
+ return _entry;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.photos.model.PhotoEntry#getId()
+ */
+ public String getId() {
+ return _entry.getId();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.photos.model.PhotoEntry#getPath()
+ */
+ public String getPath() {
+ return _entry.getPath();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Comparable#compareTo(java.lang.Object)
+ */
+ public int compareTo(PhotoEntry aEntry) {
+ return _entry.compareTo(aEntry); // TODO: is this safe?
+ }
+
+}
--- /dev/null
+/*
+ * 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 org.wamblee.security.authorization.Operation;
+
+/**
+ * Operation representing creation of an album.
+ */
+public class CreateAlbumOperation implements Operation {
+
+
+ private static final String OPERATION = "createAlbum";
+
+ /**
+ * Constructs the operation.
+ *
+ */
+ public CreateAlbumOperation() {
+ // Empty
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.security.authorization.Operation#getName()
+ */
+ public String getName() {
+ return OPERATION;
+ }
+
+}
--- /dev/null
+/*
+ * 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 org.wamblee.security.authorization.Operation;
+
+/**
+ * Operation representing upload of photos.
+ */
+public class UploadPhotosOperation implements Operation {
+
+ private static final String OPERATION = "uploadPhotos";
+
+ /**
+ * Constructs the operation.
+ */
+ public UploadPhotosOperation() {
+ // Empty.
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.security.authorization.Operation#getName()
+ */
+ public String getName() {
+ return OPERATION;
+ }
+
+}
--- /dev/null
+/*
+ * 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.filesystem;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+import org.wamblee.photos.model.PhotoEntry;
+
+/**
+ * Callback for the traverse method call of the filesystem album.
+ */
+public interface EntryFoundCallback {
+ /**
+ * Callback executed when a photo entry has been found.
+ * @param aEntries Current list of photo entries.
+ * @param aThumbnail Path of the thumbnail image.
+ * @param aPhoto Path of the full-size image.
+ * @param aPath Path in the photo album.
+ * @return True if the search should continue, false otherwise.
+ */
+ boolean photoFound(List<PhotoEntry> aEntries, File aThumbnail, File aPhoto, String aPath);
+
+ /**
+ * Callback executed when a photo album has been found.
+ * @param aEntries Current list of photo entries.
+ * @param aAlbum Path of the photo album directory.
+ * @param aPath Path in the photo album.
+ * @return True if the search should continue, false otherwise.
+ * @throws IOException In case of IO problems.
+ */
+ boolean albumFound(List<PhotoEntry> aEntries, File aAlbum, String aPath) throws IOException;
+}
--- /dev/null
+/*
+ * 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.filesystem;
+
+import java.awt.image.BufferedImage;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.wamblee.cache.Cache;
+import org.wamblee.cache.CachedObject;
+import org.wamblee.general.Pair;
+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.photos.utils.JpegUtils;
+
+/**
+ * Represents a photo album stored in a directory structure on the file system.
+ */
+public class FileSystemAlbum implements Album {
+
+ private static final Log LOG = LogFactory.getLog(FileSystemAlbum.class);
+
+ /**
+ * Subdirectory where the thumbnails are stored.
+ */
+ public static final String THUMBNAILS_DIR = "thumbnails";
+
+ /**
+ * Subdirectory where the photos are stored in their full size.
+ */
+ public static final String PHOTOS_DIR = "fotos";
+
+ /**
+ * Extension used for JPEG pictures.
+ */
+ private static final String JPG_EXTENSION = ".jpg";
+
+ /**
+ * Last part of the file name that a thumbnail must end with.
+ */
+ private static final String THUMBNAIL_ENDING = "_thumb.jpg";
+
+ private static final int THUMBNAIL_WIDTH = 100;
+
+ private static final int THUMBNAIL_HEIGHT = 100;
+
+ private static final int JPG_QUALITY = 75;
+
+ /**
+ * Array of photo entries.
+ */
+ private CachedObject<String, ArrayList<PhotoEntry>> _entries;
+
+ /**
+ * Storage directory for this album.
+ */
+ private File _dir;
+
+ /**
+ * Relative path with respect to the root album.
+ */
+ private String _path;
+
+ private class CreateEntryCallback implements EntryFoundCallback {
+ public boolean photoFound(List<PhotoEntry> aEntries, File aThumbnail,
+ File aPhoto, String aPath) {
+ PhotoEntry entry = new FileSystemPhoto(aThumbnail, aPhoto, aPath);
+ aEntries.add(entry);
+ return true;
+ }
+
+ public boolean albumFound(List<PhotoEntry> aEntries, File aAlbum,
+ String aPath) throws IOException {
+ PhotoEntry entry = new FileSystemAlbum(aAlbum, aPath,
+ _entries.getCache());
+ aEntries.add(entry);
+ return true;
+ }
+ }
+
+ /**
+ * Creates the album.
+ *
+ * @param aDir
+ * Directory where the album is located.
+ * @param aPath
+ * Path that this album represents.
+ * @param aCache
+ * Cache to use.
+ * @throws IOException
+ */
+ public FileSystemAlbum(File aDir, String aPath,
+ Cache<String, ArrayList<PhotoEntry>> aCache) throws IOException {
+ if (!aDir.isDirectory()) {
+ throw new IOException("Directory '" + aDir.getPath() +
+ "' does not exist.");
+ }
+ _dir = aDir;
+ _path = aPath;
+ _entries = new CachedObject<String, ArrayList<PhotoEntry>>(aCache,
+ aPath,
+ new CachedObject.Computation<String, ArrayList<PhotoEntry>>() {
+ public ArrayList<PhotoEntry> getObject(String aObjectKey) {
+ return FileSystemAlbum.this.compute();
+ }
+ });
+
+ }
+
+ /**
+ * Computes the photo entries for this album based on the file system.
+ *
+ * @return List of photo entries.
+ */
+ private ArrayList<PhotoEntry> compute() {
+ ArrayList<PhotoEntry> result = new ArrayList<PhotoEntry>();
+ try {
+ LOG.info("Initializing album for directory " + _dir);
+ traverse(getPath(), result, _dir, new CreateEntryCallback());
+ } catch (IOException e) {
+ LOG.fatal("IOException occurred: " + e.getMessage(), e);
+ }
+ return result;
+ }
+
+ /**
+ * Initializes the album.
+ *
+ * @param aPath
+ * Path of the album
+ * @param aEntries
+ * Photo entries for the album.
+ * @param aDir
+ * Directory where the album is stored.
+ * @throws IOException
+ */
+ static boolean traverse(String aPath, List<PhotoEntry> aEntries, File aDir,
+ EntryFoundCallback aCallback) throws IOException {
+ File[] files = listFilesInDir(aDir);
+ for (int i = 0; i < files.length; i++) {
+ File file = files[i];
+ if (file.isDirectory()) {
+
+ if (file.getName().equals(THUMBNAILS_DIR)) {
+ if (!traverseThumbnails(aPath, aEntries, aDir, aCallback,
+ file)) {
+ return false;
+ }
+ } else if (file.getName().equals(PHOTOS_DIR)) {
+ // Skip the photos directory.
+ } else {
+ // A nested album.
+ String newPath = aPath + "/" + file.getName();
+ newPath = newPath.replaceAll("//", "/");
+ if (!aCallback.albumFound(aEntries, file, newPath)) {
+ return false; // finished.
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Traverse the thumnails directory.
+ *
+ * @param aPath
+ * Path of the photo album.
+ * @param aEntries
+ * Entries of the album.
+ * @param aDir
+ * Directory of the album.
+ * @param aCallback
+ * Callback to call when a thumbnail has been found.
+ * @param aFile
+ * Directory of the photo album.
+ */
+ private static boolean traverseThumbnails(String aPath,
+ List<PhotoEntry> aEntries, File aDir, EntryFoundCallback aCallback,
+ File aFile) {
+ // Go inside the thumbnails directory to scan
+ // for available photos.
+
+ // Check if the corresponding photos directory
+ // exists
+ File photosDir = new File(aDir, PHOTOS_DIR);
+ if (photosDir.isDirectory()) {
+ if (!buildAlbum(aPath, aEntries, aFile, photosDir, aCallback)) {
+ return false;
+ }
+ } else {
+ LOG.info("Thumbnails director " + aFile.getPath() +
+ " exists but corresponding photo directory " +
+ photosDir.getPath() + " not found");
+ }
+ return true;
+ }
+
+ /**
+ * Builds up the photo album for a given thumbnails and photo directory.
+ *
+ * @param aPath
+ * Path of the album.
+ * @param aEntries
+ * Photo entries of the album.
+ * @param aThumbnailsDir
+ * Directory containing thumbnail pictures.
+ * @param aPhotosDir
+ * Directory containing full size photos.
+ */
+ private static boolean buildAlbum(String aPath, List<PhotoEntry> aEntries,
+ File aThumbnailsDir, File aPhotosDir, EntryFoundCallback aCallback) {
+
+ File[] thumbnails = listFilesInDir(aThumbnailsDir);
+ for (int i = 0; i < thumbnails.length; i++) {
+ File thumbnail = thumbnails[i];
+ if (!isThumbNail(thumbnail)) {
+ LOG.info("Skipping " + thumbnail.getPath() +
+ " because it is not a thumbnail file.");
+ continue;
+ }
+ File photo = new File(aPhotosDir, photoName(thumbnail.getName()));
+ if (!photo.isFile()) {
+ LOG.info("Photo file " + photo.getPath() + " for thumbnail " +
+ thumbnail.getPath() + " does not exist.");
+ continue;
+ }
+ String photoPath = photo.getName();
+ photoPath = photoPath.substring(0, photoPath.length() -
+ JPG_EXTENSION.length());
+ photoPath = aPath + "/" + photoPath;
+ photoPath = photoPath.replaceAll("//", "/");
+ if (!aCallback.photoFound(aEntries, thumbnail, photo, photoPath)) {
+ return false;
+ }
+ }
+ return true; // continue.
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.photos.database.PhotoEntry#getPath()
+ */
+ public String getPath() {
+ return _path;
+ }
+
+ /**
+ * Checks if the file represents a thumbnail.
+ *
+ * @param aFile
+ * File to check.
+ * @return True iff the file is a thumbnail.
+ */
+ private static boolean isThumbNail(File aFile) {
+ return aFile.getName().endsWith(THUMBNAIL_ENDING);
+ }
+
+ /**
+ * Constructs the photo name based on the thumbnail name.
+ *
+ * @param aThumbnailName
+ * Thumbnail name.
+ * @return Photo name.
+ */
+ private static String photoName(String aThumbnailName) {
+ return aThumbnailName.substring(0, aThumbnailName.length() -
+ THUMBNAIL_ENDING.length()) +
+ JPG_EXTENSION;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.photos.database.Album#getEntry(java.lang.String)
+ */
+ public PhotoEntry getEntry(String aPath) {
+ if (!aPath.startsWith("/")) {
+ throw new IllegalArgumentException(
+ "Path must start with / character");
+ }
+ if (aPath.equals("/")) {
+ return this;
+ }
+ String[] fields = aPath.substring(1).split("/");
+ return getEntry(fields, 0);
+ }
+
+ /**
+ * Gets the entry for the given path.
+ */
+ public PhotoEntry getEntry(Path aPath) {
+ return getEntry(aPath.toString());
+ }
+
+ /**
+ * Gets the entry at a given path.
+ *
+ * @param aPath
+ * Array of components of the path.
+ * @param aLevel
+ * Current level in the path.
+ * @return Entry.
+ */
+ private PhotoEntry getEntry(String[] aPath, int aLevel) {
+ String id = aPath[aLevel];
+ PhotoEntry entry = find(id);
+ if (entry == null) {
+ return entry;
+ }
+ if (aLevel < aPath.length - 1) {
+ if (!(entry instanceof Album)) {
+ return null;
+ }
+ return ((FileSystemAlbum) entry).getEntry(aPath, aLevel + 1);
+ } else {
+ return entry; // end of the path.
+ }
+ }
+
+ /**
+ * Finds an entry in the album with the given id.
+ *
+ * @param aId
+ * Photo entry id.
+ * @return Photo entry.
+ */
+ private PhotoEntry find(String aId) {
+ return findInMap(aId).getFirst();
+ }
+
+ /**
+ * Finds a photo entry in the map.
+ *
+ * @param aId
+ * Id of the photo entry.
+ * @return Pair of a photo entry and the index of the item.
+ */
+ private Pair<PhotoEntry, Integer> findInMap(String aId) {
+ List<PhotoEntry> entries = _entries.get();
+ for (int i = 0; i < entries.size(); i++) {
+ PhotoEntry entry = entries.get(i);
+ if (entry.getId().equals(aId)) {
+ return new Pair<PhotoEntry, Integer>(entry, i);
+ }
+ }
+ return new Pair<PhotoEntry, Integer>(null, -1);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.photos.database.Album#getEntry(int)
+ */
+ public PhotoEntry getEntry(int aIndex) {
+ return _entries.get().get(aIndex);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.photos.database.Album#size()
+ */
+ public int size() {
+ return _entries.get().size();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.photos.database.PhotoEntry#getId()
+ */
+ public String getId() {
+ return _dir.getName();
+ }
+
+ public String toString() {
+ return print("");
+ }
+
+ /**
+ * Prints the album with a given indentation.
+ *
+ * @param aIndent
+ * Indentation string.
+ * @return String representation of the album.
+ */
+ private String print(String aIndent) {
+ String res = aIndent + "ALBUM: " + getId() + "\n";
+ aIndent += " ";
+ for (int i = 0; i < size(); i++) {
+ PhotoEntry entry = getEntry(i);
+ if (entry instanceof FileSystemAlbum) {
+ res += ((FileSystemAlbum) entry).print(aIndent);
+ } else {
+ res += ((FileSystemPhoto) entry).print(aIndent);
+ }
+ res += "\n";
+ }
+ return res;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.photos.database.Album#addImage(java.lang.String,
+ * java.awt.Image)
+ */
+ public void addImage(String aId, InputStream aImageStream)
+ throws IOException {
+ File photoDir = getPhotoDir();
+ File thumbnailDir = getThumbnailDir();
+
+ checkPhotoDirs(aId, photoDir, thumbnailDir);
+
+ BufferedImage image;
+ try {
+ image = JpegUtils.loadJpegImage(aImageStream);
+ } catch (InterruptedException e) {
+ throw new IOException("Loading image interruptedS: " +
+ e.getMessage());
+ }
+ BufferedImage thumbnailImage = JpegUtils.scaleImage(THUMBNAIL_WIDTH,
+ THUMBNAIL_HEIGHT, image);
+
+ File thumbnail = new File(thumbnailDir, aId + THUMBNAIL_ENDING);
+ File photo = new File(photoDir, aId + JPG_EXTENSION);
+
+ writeImage(photo, image);
+ writeImage(thumbnail, thumbnailImage);
+
+ Photo photoEntry = new FileSystemPhoto(thumbnail, photo, (getPath()
+ .equals("/") ? "" : getPath()) + "/" + aId);
+ addEntry(photoEntry);
+ }
+
+ /**
+ * Checks if a given entry can be added to the album.
+ *
+ * @param aId
+ * Id of the new entry.
+ * @param aPhotoDir
+ * Photo directory.
+ * @param aThumbnailDir
+ * Thumbnail directory.
+ * @throws IOException
+ */
+ private void checkPhotoDirs(String aId, File aPhotoDir, File aThumbnailDir)
+ throws IOException {
+ // Create directories if they do not already exist.
+ aPhotoDir.mkdir();
+ aThumbnailDir.mkdir();
+
+ if (!aPhotoDir.isDirectory() || !aThumbnailDir.isDirectory()) {
+ throw new IOException("Album " + getId() +
+ " does not accept new images.");
+ }
+
+ if (getEntry("/" + aId) != null) {
+ throw new IOException("An entry with the name '" + aId +
+ "' already exists in the album " + getPath());
+ }
+ }
+
+ /**
+ * Gets the thumbnail directory for the album.
+ *
+ * @return Directory.
+ */
+ private File getThumbnailDir() {
+ File thumbnailDir = new File(_dir, THUMBNAILS_DIR);
+ return thumbnailDir;
+ }
+
+ /**
+ * Gets the photo directory for the album.
+ *
+ * @return Photo directory
+ */
+ private File getPhotoDir() {
+ File photoDir = new File(_dir, PHOTOS_DIR);
+ return photoDir;
+ }
+
+ /**
+ * Writes an image to a file.
+ *
+ * @param aFile
+ * File to write to.
+ * @param aImage
+ * Image.
+ * @throws IOException
+ */
+ private static void writeImage(File aFile, BufferedImage aImage)
+ throws IOException {
+ OutputStream output = new BufferedOutputStream(new FileOutputStream(
+ aFile));
+ JpegUtils.writeJpegImage(output, JPG_QUALITY, aImage);
+ output.close();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.photos.database.Album#addAlbum(java.lang.String)
+ */
+ public void addAlbum(String aId) throws IOException {
+ PhotoEntry entry = find(aId);
+ if (entry != null) {
+ throw new IOException("Entry already exists in album " + getId() +
+ " : " + aId);
+ }
+ // Entry not yet found. Try to create it.
+ File albumDir = new File(_dir, aId);
+ if (!albumDir.mkdir()) {
+ throw new IOException("Could not create album: " + aId);
+ }
+ File photosDir = new File(albumDir, PHOTOS_DIR);
+ if (!photosDir.mkdir()) {
+ throw new IOException("Could not create photo storage dir: " +
+ photosDir.getPath());
+ }
+ File thumbnailsDir = new File(albumDir, THUMBNAILS_DIR);
+ if (!thumbnailsDir.mkdir()) {
+ throw new IOException("Coul dnot create thumbnails storage dir: " +
+ thumbnailsDir.getPath());
+ }
+ String newPath = _path + "/" + aId;
+ newPath = newPath.replaceAll("//", "/");
+ FileSystemAlbum album = new FileSystemAlbum(albumDir, newPath,
+ _entries.getCache());
+ addEntry(album);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.photos.database.Album#removeAlbum(java.lang.String)
+ */
+ public void removeEntry(String aId) throws IOException {
+ PhotoEntry entry = find(aId);
+ if (entry == null) {
+ throw new IOException("Entry " + aId + " not found.");
+ }
+ if (entry instanceof FileSystemAlbum) {
+ // album.
+ removeAlbum((FileSystemAlbum) entry);
+ } else {
+ // regular photo
+ removePhoto(entry);
+ }
+ }
+
+ /**
+ * Removes a photo entry.
+ *
+ * @param aEntry
+ * Photo entry to remove.
+ * @throws IOException
+ */
+ private void removePhoto(PhotoEntry aEntry) throws IOException {
+ File photo = new File(_dir, PHOTOS_DIR);
+ photo = new File(photo, aEntry.getId() + JPG_EXTENSION);
+ File thumbnail = new File(_dir, THUMBNAILS_DIR);
+ thumbnail = new File(thumbnail, aEntry.getId() + THUMBNAIL_ENDING);
+ if (!photo.isFile()) {
+ throw new IOException("Photo file not found: " + photo.getPath());
+ }
+ if (!thumbnail.isFile()) {
+ throw new IOException("Thumbnail file not found: " +
+ thumbnail.getPath());
+ }
+ if (!thumbnail.delete()) {
+ throw new IOException("Could not delete thumbnail file: " +
+ thumbnail.getPath());
+ }
+ _entries.get().remove(aEntry);
+ if (!photo.delete()) {
+ throw new IOException("Could not delete photo file: " +
+ photo.getPath());
+ }
+ }
+
+ /**
+ * Removes the album entry from this album.
+ *
+ * @param aAlbum
+ * Album to remove
+ * @throws IOException
+ */
+ private void removeAlbum(FileSystemAlbum aAlbum) throws IOException {
+ if (aAlbum.size() > 0) {
+ throw new IOException("Album " + aAlbum.getId() + " not empty.");
+ }
+ // album is empty, safe to remove.
+ File photosDir = new File(aAlbum._dir, PHOTOS_DIR);
+ File thumbnailsDir = new File(aAlbum._dir, THUMBNAILS_DIR);
+ if (!photosDir.delete() || !thumbnailsDir.delete() ||
+ !aAlbum._dir.delete()) {
+ throw new IOException("Could not delete directories for album:" +
+ aAlbum.getId());
+ }
+ _entries.get().remove(aAlbum);
+ }
+
+ /**
+ * Adds an entry.
+ *
+ * @param aEntry
+ * Entry to add.
+ */
+ private void addEntry(PhotoEntry aEntry) {
+ int i = 0;
+ List<PhotoEntry> entries = _entries.get();
+ int nentries = entries.size();
+ while (i < nentries && aEntry.compareTo(entries.get(i)) > 0) {
+ i++;
+ }
+ entries.add(i, aEntry);
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Album)) {
+ return false;
+ }
+ return toString().equals(obj.toString());
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return toString().hashCode();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Comparable#compareTo(java.lang.Object)
+ */
+ public int compareTo(PhotoEntry aEntry) {
+ return getId().compareTo(((PhotoEntry) aEntry).getId());
+ }
+
+ /**
+ * Returns and alphabetically sorted list of files.
+ *
+ * @param aDir
+ * @return Sorted list of files.
+ */
+ private static File[] listFilesInDir(File aDir) {
+ File[] lResult = aDir.listFiles();
+ Arrays.sort(lResult);
+ return lResult;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.wamblee.photos.model.Album#findPhotoBefore(java.lang.String)
+ */
+ public Photo findPhotoBefore(String aId) {
+ Pair<PhotoEntry, Integer> entry = findInMap(aId);
+ if (entry.getFirst() == null) {
+ throw new IllegalArgumentException("Entry with id '" + aId +
+ "' not found in album '" + getPath() + "'");
+ }
+
+ int i = entry.getSecond() - 1;
+ List<PhotoEntry> entries = _entries.get();
+ while (i >= 0 && i < entries.size() &&
+ !(entries.get(i) instanceof Photo)) {
+ i--;
+ }
+ if (i >= 0) {
+ return (Photo) entries.get(i);
+ }
+ return null;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.wamblee.photos.model.Album#findPhotoAfter(java.lang.String)
+ */
+ public Photo findPhotoAfter(String aId) {
+ Pair<PhotoEntry, Integer> entry = findInMap(aId);
+ if (entry.getFirst() == null) {
+ throw new IllegalArgumentException("Entry with id '" + aId +
+ "' not found in album '" + getPath() + "'");
+ }
+ int i = entry.getSecond() + 1;
+ List<PhotoEntry> entries = _entries.get();
+ while (i >= 0 && i < entries.size() &&
+ !(entries.get(i) instanceof Photo)) {
+ i++;
+ }
+ if (i < entries.size()) {
+ return (Photo) entries.get(i);
+ }
+ return null;
+ }
+}
--- /dev/null
+/*
+ * 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.filesystem;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.wamblee.photos.model.Photo;
+import org.wamblee.photos.model.PhotoEntry;
+
+/**
+ * Represents a photo stored on the file system.
+ */
+public class FileSystemPhoto implements Photo {
+
+ private File _thumbnail;
+ private File _photo;
+ private String _path;
+
+ public FileSystemPhoto(File aThumbnail, File aPhoto, String aPath) {
+ _thumbnail = aThumbnail;
+ _photo = aPhoto;
+ _path = aPath;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.photos.database.PhotoEntry#getPath()
+ */
+ public String getPath() {
+ return _path;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.photos.database.Photo#getThumbNail()
+ */
+ public InputStream getThumbNail() throws IOException {
+ return openFile(_thumbnail);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.photos.database.Photo#getFile()
+ */
+ public InputStream getPhoto() throws IOException {
+ return openFile(_photo);
+ }
+
+ private InputStream openFile(File aFile) throws FileNotFoundException {
+ return new FileInputStream(aFile);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.photos.database.PhotoEntry#getId()
+ */
+ public String getId() {
+ return getId(_path);
+ }
+
+ public static String getId(String aPath) {
+ int index = aPath.lastIndexOf("/");
+ if (index < 0) {
+ return aPath;
+ }
+ return aPath.substring(index + 1);
+ }
+
+ public String toString() {
+ return print("");
+ }
+
+ String print(String aIndent) {
+ return aIndent + "IMG: " + _photo.getName();
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if ( !(obj instanceof Photo) ) {
+ return false;
+ }
+ return getPath().equals( ((Photo)obj).getPath());
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return getPath().hashCode();
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Comparable#compareTo(java.lang.Object)
+ */
+ public int compareTo(PhotoEntry aEntry) {
+ return getId().compareTo(((PhotoEntry)aEntry).getId());
+ }
+}
--- /dev/null
+/*
+ * 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.filesystem;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+import org.wamblee.cache.Cache;
+import org.wamblee.photos.model.PhotoEntry;
+
+/**
+ * Callback interface used to find a photo entry by id.
+ */
+class FindByIdCallback implements EntryFoundCallback {
+ /**
+ * Id of the entry to look for.
+ */
+ private String _id;
+
+ /**
+ * Search result or null if not found yet.
+ */
+ private PhotoEntry _entry;
+
+ /**
+ * Cache to use.
+ */
+ private Cache _cache;
+
+ /**
+ * Constructs the callback.
+ * @param aId Photo entry id to look for.
+ */
+ public FindByIdCallback(String aId, Cache aCache) {
+ _id = aId;
+ _entry = null;
+ _cache = aCache;
+ }
+
+ /**
+ * Gets the entry that was found.
+ * @return Photo entry or null if not found.
+ */
+ public PhotoEntry getEntry() {
+ return _entry;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.wamblee.photos.model.file.EntryFoundCallback#photoFound(java.io.File, java.io.File, java.lang.String)
+ */
+ public boolean photoFound(List<PhotoEntry> aEntries, File aThumbnail, File aPhoto, String aPath) {
+ if ( FileSystemPhoto.getId(aPath).equals(_id)) {
+ _entry = new FileSystemPhoto(aThumbnail, aPhoto, aPath);
+ return false;
+ }
+ return true; // continue search.
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.wamblee.photos.model.file.EntryFoundCallback#albumFound(java.io.File, java.lang.String)
+ */
+ public boolean albumFound(List<PhotoEntry> aEntries, File aAlbum, String aPath) throws IOException {
+ FileSystemAlbum albumEntry = new FileSystemAlbum(aAlbum, aPath, _cache);
+ if ( aAlbum.getName().equals(_id)) {
+ _entry = albumEntry;
+ return false;
+ }
+ return albumEntry.traverse(albumEntry.getPath(), aEntries, aAlbum, this); // continue search.
+ }
+}
--- /dev/null
+/*
+ * Copyright 2013 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.plumbing;
+
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.*;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import javax.inject.Qualifier;
+
+@Qualifier
+@Retention(RUNTIME)
+@Target({ METHOD, PARAMETER, FIELD })
+public @interface AllPhotos {
+ // Empty.
+}
--- /dev/null
+/*
+ * Copyright 2013 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.plumbing;
+
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.*;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import javax.inject.Qualifier;
+
+@Qualifier
+@Retention(RUNTIME)
+@Target({ METHOD, PARAMETER, FIELD })
+public @interface AuthorizedPhotos {
+ // Empty.
+}
--- /dev/null
+/*
+ * Copyright 2005-2013 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.plumbing;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+/**
+ * @author Erik Brakkee
+ *
+ */
+public class Configuration {
+
+ private String path;
+
+ protected Configuration() {
+ // for cdi
+ }
+
+ public Configuration(InputStream aIs) throws IOException {
+ Properties props = new Properties();
+ try {
+ props.load(aIs);
+ } finally {
+ aIs.close();
+ }
+ path = props.getProperty("org.wamblee.photos.path");
+ }
+
+ /**
+ * @return the path
+ */
+ public String getPath() {
+ return path;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005-2013 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.plumbing;
+
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.*;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(RUNTIME)
+@Target({ TYPE, METHOD })
+public @interface Eager {
+ // Empty.
+}
--- /dev/null
+/*
+ * Copyright 2005-2013 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.plumbing;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.context.spi.Context;
+import javax.enterprise.context.spi.CreationalContext;
+import javax.enterprise.event.Observes;
+import javax.enterprise.inject.spi.AfterDeploymentValidation;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.enterprise.inject.spi.Extension;
+import javax.enterprise.inject.spi.ProcessBean;
+
+public class EagerExtension implements Extension {
+
+ private List<Bean<?>> eagerBeansList = new ArrayList<Bean<?>>();
+
+ public <T> void collect(@Observes ProcessBean<T> event) {
+ if (event.getAnnotated().isAnnotationPresent(Eager.class) &&
+ event.getAnnotated().isAnnotationPresent(ApplicationScoped.class)) {
+ eagerBeansList.add(event.getBean());
+ }
+ }
+
+ public void load(@Observes AfterDeploymentValidation event,
+ BeanManager beanManager) {
+ for (Bean<?> bean : eagerBeansList) {
+ // note: toString() is important to instantiate the bean
+ /*
+ Class main = Object.class;
+ for (Type type : bean.getTypes()) {
+ if (type instanceof Class) {
+ Class clazz = (Class) type;
+ if (main.isAssignableFrom(clazz)) {
+ main = clazz;
+ }
+ }
+ }*/
+ /*
+ bean.create((CreationalContext) beanManager
+ .createCreationalContext(bean));*/
+ /* beanManager.getReference(bean, main,
+ beanManager.createCreationalContext(null)).toString();
+ beanManager.getReference(bean, main,
+ beanManager.createCreationalContext(null)).toString(); */
+ /*
+ beanManager.getReference(bean, bean.getBeanClass(),
+ beanManager.createCreationalContext(bean)).toString();
+ */
+
+ Context context = beanManager.getContext(ApplicationScoped.class);
+ context.get(bean,
+ (CreationalContext) beanManager.createCreationalContext(bean))
+ .toString();
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 2005-2013 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.plumbing;
+
+import javax.annotation.PostConstruct;
+import javax.ejb.Singleton;
+import javax.ejb.Startup;
+import javax.inject.Inject;
+
+import org.wamblee.photos.model.Album;
+import org.wamblee.security.authentication.UserAdministration;
+
+/**
+ * @author Erik Brakkee
+ *
+ */
+@Singleton
+@Startup
+public class Initializer {
+
+ @Inject
+ private UserAdministration userAdmin;
+
+ @Inject
+ @AllPhotos
+ private Album album;
+
+ @PostConstruct
+ public void init() {
+ System.out.println("Photo application initializing");
+
+ userAdmin.getUserCount();
+ album.size();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2013 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.plumbing;
+
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.*;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import javax.inject.Qualifier;
+
+@Qualifier
+@Retention(RUNTIME)
+@Target({ METHOD, PARAMETER, FIELD })
+public @interface PhotoApp {
+ // Empty.
+}
--- /dev/null
+/*
+ * Copyright 2005-2013 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.plumbing;
+
+import java.io.File;
+import java.io.IOException;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.context.SessionScoped;
+import javax.enterprise.inject.Produces;
+import javax.inject.Inject;
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.log4j.Logger;
+import org.wamblee.cache.Cache;
+import org.wamblee.cache.EhCache;
+import org.wamblee.io.ClassPathResource;
+import org.wamblee.io.InputResource;
+import org.wamblee.photos.concurrent.ConcurrentAlbum;
+import org.wamblee.photos.model.Album;
+import org.wamblee.photos.model.PhotoEntry;
+import org.wamblee.photos.model.filesystem.FileSystemAlbum;
+import org.wamblee.security.authentication.GroupSet;
+import org.wamblee.security.authentication.Md5HexMessageDigester;
+import org.wamblee.security.authentication.MessageDigester;
+import org.wamblee.security.authentication.NameValidator;
+import org.wamblee.security.authentication.RegexpNameValidator;
+import org.wamblee.security.authentication.User;
+import org.wamblee.security.authentication.UserAdminInitializer;
+import org.wamblee.security.authentication.UserAdministration;
+import org.wamblee.security.authentication.UserAdministrationImpl;
+import org.wamblee.security.authentication.UserSet;
+import org.wamblee.security.authentication.jpa.JpaGroupSet;
+import org.wamblee.security.authentication.jpa.JpaUserSet;
+
+/**
+ * @author Erik Brakkee
+ *
+ */
+public class Producer {
+
+ private static final Logger LOGGER = Logger.getLogger(Producer.class
+ .getName());
+
+ private static final String APP_CONFIG_RESOURCE = "META-INF/org.wamblee.photos.properties";
+
+ @Inject
+ private HttpServletRequest request;
+
+ @PersistenceContext
+ private EntityManager entityManager;
+
+ private Configuration getCOnfiguration() {
+ LOGGER.info("Initializing configuration");
+ Configuration config;
+ try {
+ config = new Configuration(new ClassPathResource(
+ APP_CONFIG_RESOURCE).getInputStream());
+ } catch (IOException e) {
+ throw new RuntimeException(
+ "Could not read application configuration property classpath resource " +
+ APP_CONFIG_RESOURCE, e);
+ }
+ return config;
+ }
+
+ @Produces
+ @ApplicationScoped
+ public UserAdministration getUserAdmin() {
+ LOGGER.info("Initializing user administration");
+ try {
+ NameValidator passwordvalidator = new RegexpNameValidator(".{5,}",
+ "INVALID_PASSWORD", "Password must have at least 5 characters");
+ InputResource cacheConfig = new ClassPathResource(
+ "META-INF/ehcache.xml");
+ Cache<String, User> userCache = new EhCache(cacheConfig, "users");
+ MessageDigester passwordEncoder = new Md5HexMessageDigester();
+ UserSet userset = new JpaUserSet(userCache, passwordvalidator,
+ passwordEncoder, entityManager);
+ GroupSet groupset = new JpaGroupSet(entityManager);
+ NameValidator uservalidator = new RegexpNameValidator(
+ "[a-zA-Z]+[a-zA-Z0-9]*", "INVALID_USERNAME",
+ "User name must consist of alphanumeric characters only");
+ NameValidator groupvalidator = new RegexpNameValidator(
+ "[a-zA-Z]+[a-zA-Z0-9]*", "INVALID_GROUPNAME",
+ "Group name must consist of alphanumeric characters only");
+
+ UserAdministration admin = new UserAdministrationImpl(userset,
+ groupset, uservalidator, groupvalidator);
+ UserAdminInitializer initializer = new UserAdminInitializer(admin,
+ new String[] { "erik", "admin" }, new String[] { "users",
+ "administrators" }, new String[] { "abc123", "abc123" });
+ return admin;
+ } catch (IOException e) {
+ throw new RuntimeException(
+ "Could not initialize user administration", e);
+ }
+ }
+
+ @Produces
+ @ApplicationScoped
+ @AllPhotos
+ public Album getAllPhotos() {
+ LOGGER.info("Initializing photo album");
+
+ try {
+ File dir = new File(getCOnfiguration().getPath());
+ InputResource cacheConfig = new ClassPathResource(
+ "META-INF/ehcache.xml");
+ Cache<String, ArrayList<PhotoEntry>> photoCache = new EhCache<String, ArrayList<PhotoEntry>>(
+ cacheConfig, "photos");
+ Album fileSystemAlbum = new FileSystemAlbum(dir, "/", photoCache);
+ Album concurrentAlbum = new ConcurrentAlbum(fileSystemAlbum);
+
+ return concurrentAlbum;
+ } catch (IOException e) {
+ throw new RuntimeException("Could not initialize photo album", e);
+ }
+ }
+
+ @Produces
+ @SessionScoped
+ @AuthorizedPhotos
+ public Album getAuthorizedPhotos() {
+ LOGGER.info("Initializing authorized photos for current session");
+
+ return null;
+ }
+
+ @Produces
+ @SessionScoped
+ public User getUser() {
+ LOGGER.info("Initializing user object for current session");
+
+ Principal userPrincipal = request.getUserPrincipal();
+ if (userPrincipal == null) {
+ // CDI: cannot return null from this method.
+ throw new RuntimeException("No authenticated user");
+ }
+ String username = userPrincipal.getName();
+ List<User> users = entityManager
+ .createNamedQuery(User.QUERY_FIND_BY_NAME)
+ .setParameter(User.NAME_PARAM, username).getResultList();
+ if (users.size() > 1) {
+ throw new RuntimeException("More than one user found for '" +
+ username + "'");
+ }
+ if (users.isEmpty()) {
+ throw new RuntimeException("No authenticated user");
+ }
+ return users.get(0);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005-2011 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.security;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+
+public class AuthenticationFilter implements Filter {
+ private static final String LOGINPAGE = "loginpage";
+ private static final String REQUIRED_ROLE = "role";
+ private static final String RESOURCES = "resources";
+
+ private String loginPage;
+ private String role;
+ private String resources;
+
+ public AuthenticationFilter() {
+ // Empty.
+ }
+
+ @Override
+ public void init(FilterConfig aFilterConfig) throws ServletException {
+ loginPage = aFilterConfig.getInitParameter(LOGINPAGE);
+ if (loginPage == null) {
+ throw new ServletException("No login page defined! Must specify '" +
+ LOGINPAGE + "' filter init parameter.");
+ }
+ role = aFilterConfig.getInitParameter(REQUIRED_ROLE);
+ if (role == null) {
+ throw new ServletException("No role name defined! Must specify '" +
+ REQUIRED_ROLE + "' filter init parameter.");
+ }
+ resources = aFilterConfig.getInitParameter(RESOURCES);
+ }
+
+ @Override
+ public void doFilter(ServletRequest aRequest, ServletResponse aResponse,
+ FilterChain aChain) throws IOException, ServletException {
+
+ HttpServletRequest request = (HttpServletRequest) aRequest;
+ String fullPath = request.getRequestURI();
+ String contextPath = request.getContextPath();
+ String relpath = null;
+ if (fullPath.startsWith(contextPath)) {
+ relpath = fullPath.substring(contextPath.length());
+ }
+
+ if (request.isUserInRole(role) ||
+ (resources != null && relpath != null && relpath
+ .startsWith(resources))) {
+ aChain.doFilter(aRequest, aResponse);
+ } else {
+ request.getSession().invalidate();
+ request.getRequestDispatcher(loginPage)
+ .forward(aRequest, aResponse);
+ }
+ }
+
+ @Override
+ public void destroy() {
+ // Empty.
+ }
+
+}
--- /dev/null
+/*
+ * 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.security;
+
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.persistence.DiscriminatorValue;
+import javax.persistence.Entity;
+import javax.persistence.PostLoad;
+import javax.persistence.Transient;
+
+import org.wamblee.inject.InjectorBuilder;
+import org.wamblee.photos.model.PhotoEntry;
+import org.wamblee.security.authentication.UserAdministration;
+import org.wamblee.security.authorization.AuthorizationResult;
+import org.wamblee.security.authorization.Operation;
+import org.wamblee.security.authorization.ReadOperation;
+import org.wamblee.security.authorization.UrlAuthorizationRule;
+
+/**
+ * Authorization rule for photos. A user has access to all albums owned by his
+ * own group.
+ */
+@Entity
+@DiscriminatorValue("PHOTOS")
+public class PhotoAuthorizationRule extends UrlAuthorizationRule {
+
+ @Inject
+ @Transient
+ private UserAdministration userAdmin;
+
+ /**
+ * Constructs the authorization rule.
+ *
+ */
+ public PhotoAuthorizationRule() {
+ // Empty.
+ }
+
+ @PostLoad
+ public void init() {
+ InjectorBuilder.getInjector().inject(this);
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.security.authorization.AuthorizationRule#getSupportedTypes()
+ */
+ public Class[] getSupportedTypes() {
+ return new Class[] { PhotoEntry.class };
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.security.authorization.AuthorizationRule#isAllowed(java.lang.Object, org.wamblee.security.authorization.Operation, org.wamblee.usermgt.User)
+ */
+ public AuthorizationResult isAllowed(Object aResource,
+ Operation anOperation, String aUser) {
+ if (!(aResource instanceof PhotoEntry)) {
+ return AuthorizationResult.UNSUPPORTED_RESOURCE;
+ }
+ String path = getResourcePath(aResource);
+ if (path.equals("/") && anOperation instanceof ReadOperation) {
+ return AuthorizationResult.GRANTED;
+ }
+ List<String> groups = userAdmin.getGroups(aUser);
+ for (String group : groups) {
+ String allowedPath = "/" + group;
+ if (path.startsWith(allowedPath)) {
+ return AuthorizationResult.GRANTED;
+ }
+ }
+ return AuthorizationResult.DENIED;
+ }
+
+ /**
+ * Gets the resource path for a photo entry.
+ */
+ protected String getResourcePath(Object aResource) {
+ return ((PhotoEntry) aResource).getPath();
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "PhotoAuthorizationRule()";
+ }
+}
--- /dev/null
+/*
+ * 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.utils;
+
+import java.awt.Container;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.MediaTracker;
+import java.awt.RenderingHints;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import sun.awt.image.codec.JPEGImageDecoderImpl;
+
+import com.sun.image.codec.jpeg.JPEGCodec;
+import com.sun.image.codec.jpeg.JPEGEncodeParam;
+import com.sun.image.codec.jpeg.JPEGImageDecoder;
+import com.sun.image.codec.jpeg.JPEGImageEncoder;
+
+/**
+ * Utility functions for processing JPEG images.
+ */
+public class JpegUtils {
+ /**
+ * Scales an image preserving the aspect ratio.
+ *
+ * @param aMaxWidth Maximum width.
+ * @param aMaxHeight Maximum height.
+ * @param aImage Image to scale.
+ * @return Scaled image.
+ */
+ public static BufferedImage scaleImage(int aMaxWidth, int aMaxHeight, Image aImage) {
+ double thumbRatio = (double) aMaxWidth / (double) aMaxHeight;
+ int imageWidth = aImage.getWidth(null);
+ int imageHeight = aImage.getHeight(null);
+ double imageRatio = (double) imageWidth / (double) imageHeight;
+ if (thumbRatio < imageRatio) {
+ aMaxHeight = (int) (aMaxWidth / imageRatio);
+ } else {
+ aMaxWidth = (int) (aMaxHeight * imageRatio);
+ }
+ BufferedImage thumbImage = new BufferedImage(aMaxWidth, aMaxHeight,
+ BufferedImage.TYPE_INT_RGB);
+ Graphics2D graphics2D = thumbImage.createGraphics();
+ graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+ RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+ graphics2D.drawImage(aImage, 0, 0, aMaxWidth, aMaxHeight, null);
+ return thumbImage;
+ }
+
+ /**
+ * Loads a jpeg image from an input stream.
+ *
+ * @param aInput Input stream.
+ * @return JPEG image.
+ * @throws IOException In case of IO problems.
+ * @throws InterruptedException When execution is interrupted.
+ */
+ public static BufferedImage loadJpegImage(InputStream aInput) throws IOException, InterruptedException {
+ JPEGImageDecoder decoder = new JPEGImageDecoderImpl(aInput);
+ BufferedImage image = decoder.decodeAsBufferedImage();
+ MediaTracker mediaTracker = new MediaTracker(new Container());
+ mediaTracker.addImage(image, 0);
+ mediaTracker.waitForID(0);
+ return image;
+ }
+
+ /**
+ * Writes a JPEG image.
+ *
+ * @param aOutput Output stream to write to.
+ * @param aQuality Quality of the JPEG image in the range 0..100
+ * @param aThumbImage
+ * @throws IOException
+ */
+ public static void writeJpegImage(OutputStream aOutput, int aQuality, BufferedImage aThumbImage) throws IOException {
+ // save thumbnail image to OUTFILE
+
+ if ( aQuality < 0 || aQuality > 100 ) {
+ throw new IllegalArgumentException("Argument quality must be in range 0.100: " + aQuality);
+ }
+
+ JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(aOutput);
+ JPEGEncodeParam param = encoder.getDefaultJPEGEncodeParam(aThumbImage);
+ aQuality = Math.max(0, Math.min(aQuality, 100));
+ param.setQuality((float) aQuality / 100.0f, false);
+ encoder.setJPEGEncodeParam(param);
+ encoder.encode(aThumbImage);
+ }
+
+}
--- /dev/null
+<html
+ xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.4-strict.dtd">
+<head>
+<wicket:head>
+ <title wicket:id="title">Title goes here</title>
+</wicket:head>
+</head>
+<body>
+ <div id="banner">
+ <wicket:link>
+ <img src="wamblee_logo.png" />
+ </wicket:link>
+ <span class="title">wamblee photos</span>
+ </div>
+ <div id="logout">
+ <a href="#" wicket:id="logout">Logout</a>
+ </div>
+
+
+ <div id="menu">
+ <wicket:link>
+ <ul>
+ <li><a href="HomePage.html">Home</a></li>
+ </ul>
+ </wicket:link>
+ </div>
+
+ <div id="feedback">
+ <div wicket:id="feedback"></div>
+ </div>
+
+ <div id="content">
+ <wicket:child />
+ </div>
+
+</body>
+</html>
--- /dev/null
+/*
+ * Copyright 2005-2010 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.wicket;
+
+import org.apache.wicket.markup.html.CSSPackageResource;
+import org.apache.wicket.markup.html.WebPage;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.link.Link;
+import org.apache.wicket.markup.html.panel.FeedbackPanel;
+import org.apache.wicket.model.IModel;
+import org.wamblee.wicket.behavior.TitleAttributeTooltipBehavior;
+import org.wamblee.wicket.css.ResetCssBehavior;
+import org.wamblee.wicket.page.ExpireBehavior;
+import org.wamblee.wicket.page.WebApplicationBasePage;
+
+public class BasePage extends WebApplicationBasePage {
+
+ private boolean isExpired = false;
+
+ public BasePage() {
+ this(null);
+ }
+
+ public BasePage(IModel aModel) {
+ super(aModel);
+ add(new ResetCssBehavior());
+ add(new TitleAttributeTooltipBehavior());
+ add(CSSPackageResource.getHeaderContribution(BasePage.class,
+ "photos.css"));
+ disableCaching();
+
+ add(new FeedbackPanel("feedback"));
+ add(new Label("title", getTitle()));
+
+ addBehavior(new ExpireBehavior() {
+ @Override
+ protected boolean isExpired(WebPage aPage) {
+ return isExpired;
+ }
+ });
+
+ add(new Link("logout") {
+ @Override
+ public void onClick() {
+ getRequestCycle().getSession().invalidate();
+ }
+ });
+ }
+
+ public void setExpired(boolean aExpired) {
+ isExpired = aExpired;
+ }
+
+ private String getTitle() {
+ String name = getClass().getSimpleName();
+ name = name.replaceAll("([A-Z]+)([A-Z][a-z])", "$1 $2");
+ name = name.replaceAll("[A-Z]+", " $0");
+ name = name.replaceAll("^ ", "");
+ name = name.replaceAll(" ", " ");
+ return name;
+ }
+}
--- /dev/null
+<html
+ xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.4-strict.dtd">
+<head>
+<title>Wicket Quickstart Archetype Homepage</title>
+</head>
+<body>
+<strong>Wicket Quickstart Archetype Homepage</strong>
+<br />
+<br />
+
+<wicket:extend>
+
+ <h1>The page could not be displayed</h1>
+
+</wicket:extend>
+
+</body>
+</html>
--- /dev/null
+/*
+ * Copyright 2005-2010 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.wicket;
+
+import org.apache.wicket.PageParameters;
+
+/**
+ * Homepage
+ */
+public class ErrorPage extends BasePage {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructor that is invoked when page is invoked without a session.
+ *
+ * @param parameters
+ * Page parameters
+ */
+ public ErrorPage(final PageParameters parameters) throws Exception {
+ super();
+ }
+}
--- /dev/null
+<html
+ xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.4-strict.dtd">
+<head>
+<title>Wicket Quickstart Archetype Homepage</title>
+</head>
+<body>
+<strong>Wicket Quickstart Archetype Homepage</strong>
+<br />
+<br />
+
+<wicket:extend>
+
+ <span wicket:id="message">Message here.</span>
+
+</wicket:extend>
+
+</body>
+</html>
--- /dev/null
+/*
+ * Copyright 2005-2010 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.wicket;
+
+import java.util.List;
+
+import javax.inject.Inject;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.basic.Label;
+import org.wamblee.photos.model.Album;
+import org.wamblee.photos.model.PhotoEntry;
+import org.wamblee.photos.model.plumbing.AllPhotos;
+import org.wamblee.security.authentication.User;
+import org.wamblee.security.authentication.UserAdministration;
+
+/**
+ * Homepage
+ */
+public class HomePage extends BasePage {
+
+ private static final long serialVersionUID = 1L;
+
+ @Inject
+ private User user;
+
+ @Inject
+ private UserAdministration userAdmin;
+
+ @Inject
+ @AllPhotos
+ private Album album;
+
+ /**
+ * Constructor that is invoked when page is invoked without a session.
+ *
+ * @param parameters
+ * Page parameters
+ */
+ public HomePage(final PageParameters parameters) throws Exception {
+ super();
+ add(new Label("message", "Hello world!"));
+
+ System.out.println("Currently logged in user: " + user);
+
+ List<String> usernames = userAdmin.getUsers();
+ System.out.println("All user names: " + usernames);
+
+ usernames = userAdmin.getUsers("public");
+ System.out.println("Users in group public: " + usernames);
+
+ System.out.println("Entries: " + album.size());
+ for (int i = 0; i < album.size(); i++) {
+ PhotoEntry entry = album.getEntry(i);
+ System.out.println("Entry " + i + " " + entry.getId() + " " +
+ entry.getPath());
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005-2010 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.wicket;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.wicket.Request;
+import org.apache.wicket.RequestCycle;
+import org.apache.wicket.Response;
+import org.apache.wicket.protocol.http.WebApplication;
+import org.apache.wicket.protocol.http.WebRequest;
+import org.apache.wicket.request.target.basic.RedirectRequestTarget;
+import org.apache.wicket.settings.IApplicationSettings;
+import org.wamblee.wicket.inject.ComponentInstantiationInjector;
+import org.wamblee.wicket.transactions.OpenTransactionInViewRequestCycle;
+
+/**
+ * Application object for your web application. If you want to run this
+ * application without deploying, run the Start class.
+ *
+ * @see org.wamblee.Start#main(String[])
+ */
+public class WicketApplication extends WebApplication {
+
+ /**
+ * Constructor
+ */
+ public WicketApplication() {
+ // Empty.
+ }
+
+ @Override
+ public RequestCycle newRequestCycle(Request aRequest, Response aResponse) {
+ return new OpenTransactionInViewRequestCycle(this,
+ (WebRequest) aRequest, aResponse);
+ }
+
+ @Override
+ protected void init() {
+ super.init();
+ addComponentInstantiationListener(new ComponentInstantiationInjector());
+
+ IApplicationSettings settings = getApplicationSettings();
+ settings.setInternalErrorPage(ErrorPage.class);
+
+ // Use the lines below to get the internal error page also when in
+ // development mode.
+ // IExceptionSettings exs = getExceptionSettings();
+ // exs.setUnexpectedExceptionDisplay(IExceptionSettings.SHOW_INTERNAL_ERROR_PAGE);
+ }
+
+ /**
+ * @see org.apache.wicket.Application#getHomePage()
+ */
+ public Class<HomePage> getHomePage() {
+ return HomePage.class;
+ }
+
+}
--- /dev/null
+/* general */
+body {
+ font-family: Arial, Helvetica, sans-serif;
+ list-style-type: square;
+ font-size: 20px;
+}
+/* menu styling */
+
+#menu {
+ font-size: 20px;
+ clear: both;
+}
+
+#menu ul {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ padding-top: 1em;
+}
+
+#menu li {
+ display: inline;
+ background: black;
+ padding: 4px 10px 5px 10px;
+ color: white;
+}
+
+#menu li a {
+ color: #82cafa;
+ text-decoration: none;
+}
+/* banner */
+
+#banner {
+ background: url(banner.png);
+ height: 150px;
+ width: 100%;
+}
+
+#banner img {
+ float: left;
+ margin: 0;
+ border: medium none;
+}
+
+#banner .title {
+ display: block;
+ font-size: 16px;
+ position: absolute;
+ text-align: center;
+ top: 135px;
+ width: 178px;
+}
+
+#logout {
+ display: block;
+ position: absolute;
+ top: 20px;
+ left: 220px;
+}
+
+/* feedback panel */
+
+ul.feedbackPanel {
+ color: red;
+}
+/* the contents on the main page */
+
+#content {
+ clear: both;
+ margin-top: 1em;
+}
+
+#content em {
+ display: block;
+ margin-bottom: 0px;
+ font-style: normal;
+ font-weight: bold;
+ font-size: 24px;
+}
+
+#content table {
+ text-align: left;
+}
+
+#content th, #content td {
+ vertical-align: top;
+ padding: 10px;
+}
+
+.entries a {
+ display: block;
+ float: left;
+ font-size: 24px;
+ margin-left: 10%;
+ margin-right: 24px;
+ line-height: 34px;
+ width: 100%;
+}
+
+.entries .index {
+ display: block;
+ float: left;
+ font-size: 30px;
+ width: 100%;
+ margin-top: 10px;
+}
+
+
+
--- /dev/null
+<beans xmlns="http://java.sun.com/xml/ns/javaee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
+
+</beans>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<web-app xmlns="http://java.sun.com/xml/ns/javaee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+ version="3.0">
+
+ <display-name>cdi</display-name>
+
+ <!--
+ There are three means to configure Wickets configuration mode and they are
+ tested in the order given.
+ 1) A system property: -Dwicket.configuration
+ 2) servlet specific <init-param>
+ 3) context specific <context-param>
+ The value might be either "development" (reloading when templates change)
+ or "deployment". If no configuration is found, "development" is the default.
+ -->
+
+ <filter>
+ <filter-name>authentication</filter-name>
+ <filter-class>org.wamblee.photos.security.AuthenticationFilter</filter-class>
+ <init-param>
+ <param-name>loginpage</param-name>
+ <param-value>/login.jsp</param-value>
+ </init-param>
+ <!-- each authenticated user is assigned to the gruop ALL in the security realm configuration -->
+ <init-param>
+ <param-name>role</param-name>
+ <param-value>ALL</param-value>
+ </init-param>
+ <!-- defines the resource URLs for which no authentication is required -->
+ <init-param>
+ <param-name>resources</param-name>
+ <param-value>/resources</param-value>
+ </init-param>
+ </filter>
+
+ <filter>
+ <filter-name>photos</filter-name>
+ <filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class>
+ <init-param>
+ <param-name>applicationClassName</param-name>
+ <param-value>org.wamblee.photos.wicket.WicketApplication</param-value>
+ </init-param>
+ <init-param>
+ <param-name>configuration</param-name>
+ <param-value>development</param-value>
+ </init-param>
+ </filter>
+
+ <filter-mapping>
+ <filter-name>authentication</filter-name>
+ <url-pattern>/*</url-pattern>
+ </filter-mapping>
+ <filter-mapping>
+ <filter-name>photos</filter-name>
+ <url-pattern>/*</url-pattern>
+ </filter-mapping>
+
+ <security-constraint>
+ <web-resource-collection>
+ <web-resource-name>resources</web-resource-name>
+ <url-pattern>/resources/*</url-pattern>
+ </web-resource-collection>
+ </security-constraint>
+ <security-constraint>
+ <web-resource-collection>
+ <web-resource-name>securedaccess</web-resource-name>
+ <url-pattern>/*</url-pattern>
+ </web-resource-collection>
+ <auth-constraint>
+ <role-name>ALL</role-name>
+ <role-name>users</role-name>
+ </auth-constraint>
+ </security-constraint>
+
+ <login-config>
+ <auth-method>FORM</auth-method>
+ <realm-name>PhotoXChangeRealm</realm-name>
+ <form-login-config>
+ <form-login-page>/login.jsp</form-login-page>
+ <form-error-page>/loginError.jsp</form-error-page>
+ </form-login-config>
+ </login-config>
+
+ <session-config>
+ <session-timeout>10</session-timeout>
+ </session-config>
+
+ <welcome-file-list>
+ <welcome-file>login.jsp</welcome-file>
+ </welcome-file-list>
+
+ <security-role>
+ <role-name>ALL</role-name>
+ </security-role>
+
+</web-app>
--- /dev/null
+<%@ page language="java" pageEncoding="UTF-8" session="true"%>
+
+
+
+<%
+ String path = request.getContextPath();
+ String basePath = request.getScheme() + "://" +
+ request.getServerName() + ":" + request.getServerPort() + path +
+ "/";
+%>
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+
+<head>
+<title wicket:id="title">Home Page</title>
+
+
+<link rel="stylesheet" type="text/css"
+ href="resources/org.wamblee.photos.wicket.BasePage/photos.css" />
+</head>
+
+<body>
+
+ <div id="banner">
+ <wicket:link>
+ <img
+ src="resources/org.wamblee.photos.wicket.BasePage/wamblee_logo.png" />
+ </wicket:link>
+ <span class="title">wamblee photos</span>
+ </div>
+
+
+ Please login:
+ <form method="POST" action="j_security_check">
+ <table>
+ <tr>
+ <td>Username:</td>
+ <td><input type="text" name="j_username" />
+ </td>
+ </tr>
+ <tr>
+ <td>Password:</td>
+ <td><input type="password" name="j_password" autocomplete="off"/>
+ </td>
+ </tr>
+ <tr>
+ <td><input type="submit" value="Login" />
+ </td>
+ <td></td>
+ </tr>
+ </table>
+ </form>
+
+</body>
+</html>
--- /dev/null
+
+<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
+<%
+String path = request.getContextPath();
+String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
+%>
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+ <head>
+ </head>
+
+ <body>
+ Wrong user id or password specified. <br/>
+ <jsp:include page="login.jsp"></jsp:include>
+ </body>
+</html>
--- /dev/null
+
+
+<%
+ session.invalidate();
+ response.sendRedirect( "" );
+%>
\ No newline at end of file
--- /dev/null
+
+- modify database table names.
+
+rename USERS to SEC_USER;
+rename table GROUPS to SEC_GROUP;
+rename table USER_GROUPS to SEC_USER_GROUP;
+
+- modify config of flexible jdbc realm.
+
+ * db query for users
+ * db query for groups
+
+- modify database schema.
+
+rename table USER_CONDITIONS to SEC_USER_CONDITION;
+update SEC_USER_CONDITION set TYPE = 'ANYUSER' where TYPE = 'ANY';
+update SEC_USER_CONDITION set TYPE = 'GROUP' where TYPE = 'URL';
+
+alter table SEC_USER_CONDITION change GROUPNAME GRP varchar(255);
+rename table OPERATION_CONDITIONS to SEC_OPERATION_CONDITION;
+alter table SEC_OPERATION_CONDITION change OPERATION CLASSNAME varchar(255);
+rename table PATH_CONDITIONS to SEC_PATH_CONDITION;
+rename table AUTHORIZATION_RULES to SEC_AUTH_RULE;
+alter table SEC_AUTH_RULE change RESULT AUTH_RESULT varchar(255);
+alter table SEC_AUTH_RULE change RESOURCE_CLASSNAME RES_CLASSNAME varchar(255);
+
+alter table SEC_AUTH_RULE drop foreign key FK422045712468C452;
+alter table SEC_AUTH_RULE drop foreign key FK42204571FD5B8562;
+alter table SEC_AUTH_RULE drop foreign key FK422045712A7093A2;
+
+alter table SEC_AUTH_RULE change USERCONDITION_ID USER_COND_PK bigint(20);
+alter table SEC_AUTH_RULE change OPERATIONCONDITION_ID OPER_COND_PK bigint(20);
+alter table SEC_AUTH_RULE change PATHCONDITION_ID PATH_COND_PK bigint(20);
+
+alter table SEC_AUTH_RULE drop key FK422045712468C452;
+alter table SEC_AUTH_RULE drop key FK42204571FD5B8562;
+alter table SEC_AUTH_RULE drop key FK422045712A7093A2;
+
+alter table SEC_AUTH_RULE add foreign key USER_COND_KEY(USER_COND_PK) references SEC_USER_CONDITION (ID);
+alter table SEC_AUTH_RULE add foreign key OPER_COND_KEY(OPER_COND_PK) references SEC_OPERATION_CONDITION (ID);
+alter table SEC_AUTH_RULE add foreign key PATH_COND_KEY(PATH_COND_PK) references SEC_PATH_CONDITION (ID);
+
+update SEC_AUTH_RULE set RES_CLASSNAME = 'org.wamblee.photos.wicket.BasePage' where RES_CLASSNAME like '%tapestry%';
+
+rename table AUTHORIZATION_SERVICE to SEC_AUTH_SVC;
+rename table AUTHORIZATION_SERVICE to SEC_AUTH_SVC;
+
+alter table SEC_AUTH_SVC_RULE drop foreign key FK7DFDBD476CC274C0;
+alter table SEC_AUTH_SVC_RULE drop key FK7DFDBD476CC274C0;
+alter table SEC_AUTH_SVC_RULE drop foreign key FK7DFDBD47C0669E1B;
+alter table SEC_AUTH_SVC_RULE drop key FK7DFDBD47C0669E1B;
+
+alter table SEC_AUTH_SVC_RULE change ID SVC_ID bigint(20);
+alter table SEC_AUTH_SVC_RULE change POSITION RULE_INDEX int(11);
+
+alter table SEC_AUTH_SVC_RULE add foreign key SVCKEY(SVC_ID) references SEC_AUTH_SVC(ID);
+alter table SEC_AUTH_SVC_RULE add foreign key RULEKEY(RULE_ID) references SEC_AUTH_RULE(ID);
+
+
+
+=======================
+
+| SEC_AUTH_RULE | CREATE TABLE `SEC_AUTH_RULE` (
+ `ID` bigint(20) NOT NULL AUTO_INCREMENT,
+ `TYPE` varchar(255) NOT NULL DEFAULT '',
+ `VERSION` int(11) NOT NULL DEFAULT '0',
+ `AUTH_RESULT` varchar(255) DEFAULT NULL,
+ `RES_CLASSNAME` varchar(255) DEFAULT NULL,
+ `USERCONDITION_ID` bigint(20) DEFAULT NULL,
+ `PATHCONDITION_ID` bigint(20) DEFAULT NULL,
+ `OPERATIONCONDITION_ID` bigint(20) DEFAULT NULL,
+ PRIMARY KEY (`ID`),
+ KEY `FK422045712468C452` (`OPERATIONCONDITION_ID`),
+ KEY `FK42204571FD5B8562` (`USERCONDITION_ID`),
+ KEY `FK422045712A7093A2` (`PATHCONDITION_ID`),
+ CONSTRAINT `FK422045712468C452` FOREIGN KEY (`OPERATIONCONDITION_ID`) REFERENCES `SEC_OPERATION_CONDITION` (`ID`),
+ CONSTRAINT `FK422045712A7093A2` FOREIGN KEY (`PATHCONDITION_ID`) REFERENCES `SEC_PATH_CONDITION` (`ID`),
+ CONSTRAINT `FK42204571FD5B8562` FOREIGN KEY (`USERCONDITION_ID`) REFERENCES `SEC_USER_CONDITION` (`ID`)
+
+
+show create table SEC_AUTH_SVC_RULE;
++-------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+| Table | Create Table |
++-------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+| SEC_AUTH_SVC_RULE | CREATE TABLE `SEC_AUTH_SVC_RULE` (
+ `ID` bigint(20) NOT NULL DEFAULT '0',
+ `RULE_ID` bigint(20) NOT NULL DEFAULT '0',
+ `POSITION` int(11) NOT NULL DEFAULT '0',
+ PRIMARY KEY (`ID`,`POSITION`),
+ KEY `FK7DFDBD476CC274C0` (`ID`),
+ KEY `FK7DFDBD47C0669E1B` (`RULE_ID`),
+ CONSTRAINT `FK7DFDBD476CC274C0` FOREIGN KEY (`ID`) REFERENCES `SEC_AUTH_SVC` (`ID`),
+ CONSTRAINT `FK7DFDBD47C0669E1B` FOREIGN KEY (`RULE_ID`) REFERENCES `SEC_AUTH_RULE` (`ID`)
+) ENGINE=InnoDB DEFAULT CHARSET=latin1 |
+
+
+