From 8845e7fe6141ccc98fd070ee4e653941f6e60508 Mon Sep 17 00:00:00 2001 From: Erik Brakkee Date: Sat, 14 Sep 2013 18:25:23 +0200 Subject: [PATCH] just before adding authorization service. --- jaccmagic.txt | 33 + pom.xml | 331 +++++++- src/main/java/META-INF/ehcache.xml | 75 ++ .../META-INF/org.wamblee.photos.properties | 6 + src/main/java/META-INF/persistence.xml | 39 + .../javax.enterprise.inject.spi.Extension | 1 + .../photos/concurrent/ConcurrentAlbum.java | 159 ++++ .../photos/concurrent/ConcurrentPhoto.java | 63 ++ .../concurrent/ConcurrentPhotoEntry.java | 86 +++ .../java/org/wamblee/photos/model/Album.java | 100 +++ .../java/org/wamblee/photos/model/Path.java | 161 ++++ .../java/org/wamblee/photos/model/Photo.java | 39 + .../org/wamblee/photos/model/PhotoEntry.java | 34 + .../model/authorization/AuthorizedAlbum.java | 233 ++++++ .../model/authorization/AuthorizedPhoto.java | 53 ++ .../authorization/AuthorizedPhotoEntry.java | 62 ++ .../authorization/CreateAlbumOperation.java | 44 ++ .../authorization/UploadPhotosOperation.java | 42 + .../model/filesystem/EntryFoundCallback.java | 47 ++ .../model/filesystem/FileSystemAlbum.java | 731 ++++++++++++++++++ .../model/filesystem/FileSystemPhoto.java | 123 +++ .../model/filesystem/FindByIdCallback.java | 86 +++ .../photos/model/plumbing/AllPhotos.java | 31 + .../model/plumbing/AuthorizedPhotos.java | 31 + .../photos/model/plumbing/Configuration.java | 51 ++ .../wamblee/photos/model/plumbing/Eager.java | 28 + .../photos/model/plumbing/EagerExtension.java | 74 ++ .../photos/model/plumbing/Initializer.java | 48 ++ .../photos/model/plumbing/PhotoApp.java | 31 + .../photos/model/plumbing/Producer.java | 172 +++++ .../photos/security/AuthenticationFilter.java | 84 ++ .../security/PhotoAuthorizationRule.java | 103 +++ .../org/wamblee/photos/utils/JpegUtils.java | 106 +++ .../org/wamblee/photos/wicket/BasePage.html | 37 + .../org/wamblee/photos/wicket/BasePage.java | 75 ++ .../org/wamblee/photos/wicket/ErrorPage.html | 18 + .../org/wamblee/photos/wicket/ErrorPage.java | 36 + .../org/wamblee/photos/wicket/HomePage.html | 18 + .../org/wamblee/photos/wicket/HomePage.java | 72 ++ .../photos/wicket/WicketApplication.java | 72 ++ .../java/org/wamblee/photos/wicket/banner.png | Bin 0 -> 210 bytes .../wamblee/photos/wicket/photos-print.css | 0 .../java/org/wamblee/photos/wicket/photos.css | 110 +++ .../wamblee/photos/wicket/wamblee_logo.png | Bin 0 -> 16429 bytes src/main/webapp/WEB-INF/beans.xml | 5 + src/main/webapp/WEB-INF/web.xml | 98 +++ src/main/webapp/login.jsp | 56 ++ src/main/webapp/loginError.jsp | 17 + src/main/webapp/logout.jsp | 6 + todo.txt | 97 +++ 50 files changed, 4023 insertions(+), 1 deletion(-) create mode 100644 jaccmagic.txt create mode 100644 src/main/java/META-INF/ehcache.xml create mode 100644 src/main/java/META-INF/org.wamblee.photos.properties create mode 100644 src/main/java/META-INF/persistence.xml create mode 100644 src/main/java/META-INF/services/javax.enterprise.inject.spi.Extension create mode 100644 src/main/java/org/wamblee/photos/concurrent/ConcurrentAlbum.java create mode 100644 src/main/java/org/wamblee/photos/concurrent/ConcurrentPhoto.java create mode 100644 src/main/java/org/wamblee/photos/concurrent/ConcurrentPhotoEntry.java create mode 100644 src/main/java/org/wamblee/photos/model/Album.java create mode 100644 src/main/java/org/wamblee/photos/model/Path.java create mode 100644 src/main/java/org/wamblee/photos/model/Photo.java create mode 100644 src/main/java/org/wamblee/photos/model/PhotoEntry.java create mode 100644 src/main/java/org/wamblee/photos/model/authorization/AuthorizedAlbum.java create mode 100644 src/main/java/org/wamblee/photos/model/authorization/AuthorizedPhoto.java create mode 100644 src/main/java/org/wamblee/photos/model/authorization/AuthorizedPhotoEntry.java create mode 100644 src/main/java/org/wamblee/photos/model/authorization/CreateAlbumOperation.java create mode 100644 src/main/java/org/wamblee/photos/model/authorization/UploadPhotosOperation.java create mode 100644 src/main/java/org/wamblee/photos/model/filesystem/EntryFoundCallback.java create mode 100644 src/main/java/org/wamblee/photos/model/filesystem/FileSystemAlbum.java create mode 100644 src/main/java/org/wamblee/photos/model/filesystem/FileSystemPhoto.java create mode 100644 src/main/java/org/wamblee/photos/model/filesystem/FindByIdCallback.java create mode 100644 src/main/java/org/wamblee/photos/model/plumbing/AllPhotos.java create mode 100644 src/main/java/org/wamblee/photos/model/plumbing/AuthorizedPhotos.java create mode 100644 src/main/java/org/wamblee/photos/model/plumbing/Configuration.java create mode 100644 src/main/java/org/wamblee/photos/model/plumbing/Eager.java create mode 100644 src/main/java/org/wamblee/photos/model/plumbing/EagerExtension.java create mode 100644 src/main/java/org/wamblee/photos/model/plumbing/Initializer.java create mode 100644 src/main/java/org/wamblee/photos/model/plumbing/PhotoApp.java create mode 100644 src/main/java/org/wamblee/photos/model/plumbing/Producer.java create mode 100644 src/main/java/org/wamblee/photos/security/AuthenticationFilter.java create mode 100644 src/main/java/org/wamblee/photos/security/PhotoAuthorizationRule.java create mode 100644 src/main/java/org/wamblee/photos/utils/JpegUtils.java create mode 100644 src/main/java/org/wamblee/photos/wicket/BasePage.html create mode 100644 src/main/java/org/wamblee/photos/wicket/BasePage.java create mode 100644 src/main/java/org/wamblee/photos/wicket/ErrorPage.html create mode 100644 src/main/java/org/wamblee/photos/wicket/ErrorPage.java create mode 100644 src/main/java/org/wamblee/photos/wicket/HomePage.html create mode 100644 src/main/java/org/wamblee/photos/wicket/HomePage.java create mode 100644 src/main/java/org/wamblee/photos/wicket/WicketApplication.java create mode 100644 src/main/java/org/wamblee/photos/wicket/banner.png create mode 100644 src/main/java/org/wamblee/photos/wicket/photos-print.css create mode 100644 src/main/java/org/wamblee/photos/wicket/photos.css create mode 100644 src/main/java/org/wamblee/photos/wicket/wamblee_logo.png create mode 100644 src/main/webapp/WEB-INF/beans.xml create mode 100644 src/main/webapp/WEB-INF/web.xml create mode 100644 src/main/webapp/login.jsp create mode 100644 src/main/webapp/loginError.jsp create mode 100644 src/main/webapp/logout.jsp create mode 100644 todo.txt diff --git a/jaccmagic.txt b/jaccmagic.txt new file mode 100644 index 0000000..decf99e --- /dev/null +++ b/jaccmagic.txt @@ -0,0 +1,33 @@ + // 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 roleSet = new HashSet(); + Enumeration 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); diff --git a/pom.xml b/pom.xml index 971a5a4..6a00ea2 100644 --- a/pom.xml +++ b/pom.xml @@ -1,2 +1,331 @@ -janse + + 4.0.0 + org.wamblee.photos + wamblee-photos + war + 0.1-SNAPSHOT + photos + + Photos application. + + + 1.4.9 + 6.1.4 + 0.6 + + + + http://wamblee.org + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + http://wamblee.org/gitweb/photos + scm:git:https://wamblee.org/git/public/photos + scm:git:https://wamblee.org/git/public/photos + + + + erik@wamblee.org + Erik Brakkee + http://brakkee.org + + + + + + + javax.portlet + portlet-api + 2.0 + + + + + dom4j + dom4j + 1.6 + + + xml-apis + xml-apis + + + + + jaxen + jaxen + 1.1-beta-9 + + + xom + xom + + + xerces + xmlParserAPIs + + + + + + + + org.wamblee + wamblee-wicket-joe + ${utils.version} + + + org.wamblee + wamblee-wicket-components + ${utils.version} + + + org.wamblee + wamblee-support-cdi + ${utils.version} + + + org.wamblee + wamblee-support-cdi + test-jar + test + ${utils.version} + + + org.wamblee + wamblee-security-usermgt + ${utils.version} + + + + + + org.apache.wicket + wicket + ${wicket.version} + + + + + + org.slf4j + slf4j-log4j12 + 1.4.2 + + + log4j + log4j + 1.2.14 + + + + + junit + junit + 4.4 + test + + + org.mockito + mockito-all + 1.8.5 + test + + + + + + org.mortbay.jetty + jetty + ${jetty.version} + provided + + + org.mortbay.jetty + jetty-util + ${jetty.version} + provided + + + org.mortbay.jetty + jetty-management + ${jetty.version} + provided + + + + + org.wamblee + wamblee-test-enterprise + ${utils.version} + test + + + org.wamblee + wamblee-test-eclipselink + ${utils.version} + test + + + org.eclipse.persistence + javax.persistence + 2.0.0 + provided + + + + org.eclipse.persistence + eclipselink + 2.0.2 + test + + + + javax + javaee-api + 6.0 + provided + + + + + + + + false + src/main/resources + + + false + src/main/java + + ** + + + **/*.java + + + + + + false + src/test/java + + ** + + + **/*.java + + + + + + org.apache.maven.plugins + maven-war-plugin + 2.0 + + + + + + maven-dependency-plugin + + + copy-dependencies + package + + copy-dependencies + + + + + + + true + org.apache.maven.plugins + maven-compiler-plugin + + 1.6 + 1.6 + true + true + -XDignore.symbol.file + + + + org.mortbay.jetty + maven-jetty-plugin + + + + + + + + + EclipseLink Repo + http://www.eclipse.org/downloads/download.php?r=1&nf=1&file=/rt/eclipselink/maven.repo + + + + + + release + + + performRelease + true + + + + + + + org.apache.maven.plugins + maven-release-plugin + 2.0 + + true + javadoc:jar deploy + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + + + + + + + sonatype-nexus-staging + Nexus Release Repository + http://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + diff --git a/src/main/java/META-INF/ehcache.xml b/src/main/java/META-INF/ehcache.xml new file mode 100644 index 0000000..ee79950 --- /dev/null +++ b/src/main/java/META-INF/ehcache.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + diff --git a/src/main/java/META-INF/org.wamblee.photos.properties b/src/main/java/META-INF/org.wamblee.photos.properties new file mode 100644 index 0000000..75073f1 --- /dev/null +++ b/src/main/java/META-INF/org.wamblee.photos.properties @@ -0,0 +1,6 @@ + +############################################################################## +# Full path name of the physical storage of the photo albums. +############################################################################## +org.wamblee.photos.path=/home/erik/java/workspace/album + diff --git a/src/main/java/META-INF/persistence.xml b/src/main/java/META-INF/persistence.xml new file mode 100644 index 0000000..0a4525e --- /dev/null +++ b/src/main/java/META-INF/persistence.xml @@ -0,0 +1,39 @@ + + + + jdbc/PhotoXChange + + + org.wamblee.security.authentication.User + org.wamblee.security.authentication.Group + + + + + + + + true + + + \ No newline at end of file diff --git a/src/main/java/META-INF/services/javax.enterprise.inject.spi.Extension b/src/main/java/META-INF/services/javax.enterprise.inject.spi.Extension new file mode 100644 index 0000000..ee6fcf8 --- /dev/null +++ b/src/main/java/META-INF/services/javax.enterprise.inject.spi.Extension @@ -0,0 +1 @@ +org.wamblee.photos.model.plumbing.EagerExtension diff --git a/src/main/java/org/wamblee/photos/concurrent/ConcurrentAlbum.java b/src/main/java/org/wamblee/photos/concurrent/ConcurrentAlbum.java new file mode 100644 index 0000000..47520a2 --- /dev/null +++ b/src/main/java/org/wamblee/photos/concurrent/ConcurrentAlbum.java @@ -0,0 +1,159 @@ +/* + * 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 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(); + } + } +} diff --git a/src/main/java/org/wamblee/photos/concurrent/ConcurrentPhoto.java b/src/main/java/org/wamblee/photos/concurrent/ConcurrentPhoto.java new file mode 100644 index 0000000..bf2357b --- /dev/null +++ b/src/main/java/org/wamblee/photos/concurrent/ConcurrentPhoto.java @@ -0,0 +1,63 @@ +/* + * 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(); + } + } +} diff --git a/src/main/java/org/wamblee/photos/concurrent/ConcurrentPhotoEntry.java b/src/main/java/org/wamblee/photos/concurrent/ConcurrentPhotoEntry.java new file mode 100644 index 0000000..0691a76 --- /dev/null +++ b/src/main/java/org/wamblee/photos/concurrent/ConcurrentPhotoEntry.java @@ -0,0 +1,86 @@ +/* + * 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(); + } + } + +} diff --git a/src/main/java/org/wamblee/photos/model/Album.java b/src/main/java/org/wamblee/photos/model/Album.java new file mode 100644 index 0000000..126f161 --- /dev/null +++ b/src/main/java/org/wamblee/photos/model/Album.java @@ -0,0 +1,100 @@ +/* + * 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); +} diff --git a/src/main/java/org/wamblee/photos/model/Path.java b/src/main/java/org/wamblee/photos/model/Path.java new file mode 100644 index 0000000..974e47a --- /dev/null +++ b/src/main/java/org/wamblee/photos/model/Path.java @@ -0,0 +1,161 @@ +/* + * 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); + } +} diff --git a/src/main/java/org/wamblee/photos/model/Photo.java b/src/main/java/org/wamblee/photos/model/Photo.java new file mode 100644 index 0000000..8405c23 --- /dev/null +++ b/src/main/java/org/wamblee/photos/model/Photo.java @@ -0,0 +1,39 @@ +/* + * 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; +} diff --git a/src/main/java/org/wamblee/photos/model/PhotoEntry.java b/src/main/java/org/wamblee/photos/model/PhotoEntry.java new file mode 100644 index 0000000..b360eaf --- /dev/null +++ b/src/main/java/org/wamblee/photos/model/PhotoEntry.java @@ -0,0 +1,34 @@ +/* + * 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 { + + /** + * 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(); +} diff --git a/src/main/java/org/wamblee/photos/model/authorization/AuthorizedAlbum.java b/src/main/java/org/wamblee/photos/model/authorization/AuthorizedAlbum.java new file mode 100644 index 0000000..7406d99 --- /dev/null +++ b/src/main/java/org/wamblee/photos/model/authorization/AuthorizedAlbum.java @@ -0,0 +1,233 @@ +/* + * Copyright 2005 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.wamblee.photos.model.authorization; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import org.apache.log4j.Logger; +import org.wamblee.cache.Cache; +import org.wamblee.cache.CachedObject; +import org.wamblee.photos.model.Album; +import org.wamblee.photos.model.Path; +import org.wamblee.photos.model.Photo; +import org.wamblee.photos.model.PhotoEntry; +import org.wamblee.security.authorization.AllOperation; +import org.wamblee.security.authorization.AuthorizationService; +import org.wamblee.security.authorization.DeleteOperation; +import org.wamblee.security.authorization.ReadOperation; +import org.wamblee.security.authorization.WriteOperation; + +/** + * Decorator for an album providing defined behavior when used in a concurrent + * setting. + */ +public class AuthorizedAlbum extends AuthorizedPhotoEntry implements Album { + + private static final Logger LOGGER = Logger + .getLogger(AuthorizedAlbum.class); + + private AuthorizationService _authorizer; + + private CachedObject> _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>( + aCache, aSessionId + "/" + aAlbum.getPath(), + new CachedObject.Computation>() { + public ArrayList 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 compute() { + LOGGER.info("Refreshing cache " + getPath()); + ArrayList result = new ArrayList(); + 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 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 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() + "')"; + } +} diff --git a/src/main/java/org/wamblee/photos/model/authorization/AuthorizedPhoto.java b/src/main/java/org/wamblee/photos/model/authorization/AuthorizedPhoto.java new file mode 100644 index 0000000..cda649d --- /dev/null +++ b/src/main/java/org/wamblee/photos/model/authorization/AuthorizedPhoto.java @@ -0,0 +1,53 @@ +/* + * 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(); + } +} diff --git a/src/main/java/org/wamblee/photos/model/authorization/AuthorizedPhotoEntry.java b/src/main/java/org/wamblee/photos/model/authorization/AuthorizedPhotoEntry.java new file mode 100644 index 0000000..e260b44 --- /dev/null +++ b/src/main/java/org/wamblee/photos/model/authorization/AuthorizedPhotoEntry.java @@ -0,0 +1,62 @@ +/* + * 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? + } + +} diff --git a/src/main/java/org/wamblee/photos/model/authorization/CreateAlbumOperation.java b/src/main/java/org/wamblee/photos/model/authorization/CreateAlbumOperation.java new file mode 100644 index 0000000..75fb9f9 --- /dev/null +++ b/src/main/java/org/wamblee/photos/model/authorization/CreateAlbumOperation.java @@ -0,0 +1,44 @@ +/* + * 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; + } + +} diff --git a/src/main/java/org/wamblee/photos/model/authorization/UploadPhotosOperation.java b/src/main/java/org/wamblee/photos/model/authorization/UploadPhotosOperation.java new file mode 100644 index 0000000..5d8907d --- /dev/null +++ b/src/main/java/org/wamblee/photos/model/authorization/UploadPhotosOperation.java @@ -0,0 +1,42 @@ +/* + * 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; + } + +} diff --git a/src/main/java/org/wamblee/photos/model/filesystem/EntryFoundCallback.java b/src/main/java/org/wamblee/photos/model/filesystem/EntryFoundCallback.java new file mode 100644 index 0000000..6669f32 --- /dev/null +++ b/src/main/java/org/wamblee/photos/model/filesystem/EntryFoundCallback.java @@ -0,0 +1,47 @@ +/* + * 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 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 aEntries, File aAlbum, String aPath) throws IOException; +} diff --git a/src/main/java/org/wamblee/photos/model/filesystem/FileSystemAlbum.java b/src/main/java/org/wamblee/photos/model/filesystem/FileSystemAlbum.java new file mode 100644 index 0000000..68a8faf --- /dev/null +++ b/src/main/java/org/wamblee/photos/model/filesystem/FileSystemAlbum.java @@ -0,0 +1,731 @@ +/* + * 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> _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 aEntries, File aThumbnail, + File aPhoto, String aPath) { + PhotoEntry entry = new FileSystemPhoto(aThumbnail, aPhoto, aPath); + aEntries.add(entry); + return true; + } + + public boolean albumFound(List 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> aCache) throws IOException { + if (!aDir.isDirectory()) { + throw new IOException("Directory '" + aDir.getPath() + + "' does not exist."); + } + _dir = aDir; + _path = aPath; + _entries = new CachedObject>(aCache, + aPath, + new CachedObject.Computation>() { + public ArrayList 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 compute() { + ArrayList result = new ArrayList(); + 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 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 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 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 findInMap(String aId) { + List entries = _entries.get(); + for (int i = 0; i < entries.size(); i++) { + PhotoEntry entry = entries.get(i); + if (entry.getId().equals(aId)) { + return new Pair(entry, i); + } + } + return new Pair(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 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 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 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 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 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; + } +} diff --git a/src/main/java/org/wamblee/photos/model/filesystem/FileSystemPhoto.java b/src/main/java/org/wamblee/photos/model/filesystem/FileSystemPhoto.java new file mode 100644 index 0000000..ec1452b --- /dev/null +++ b/src/main/java/org/wamblee/photos/model/filesystem/FileSystemPhoto.java @@ -0,0 +1,123 @@ +/* + * 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()); + } +} diff --git a/src/main/java/org/wamblee/photos/model/filesystem/FindByIdCallback.java b/src/main/java/org/wamblee/photos/model/filesystem/FindByIdCallback.java new file mode 100644 index 0000000..054ba5f --- /dev/null +++ b/src/main/java/org/wamblee/photos/model/filesystem/FindByIdCallback.java @@ -0,0 +1,86 @@ +/* + * 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 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 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. + } +} diff --git a/src/main/java/org/wamblee/photos/model/plumbing/AllPhotos.java b/src/main/java/org/wamblee/photos/model/plumbing/AllPhotos.java new file mode 100644 index 0000000..4da6b55 --- /dev/null +++ b/src/main/java/org/wamblee/photos/model/plumbing/AllPhotos.java @@ -0,0 +1,31 @@ +/* + * 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. +} diff --git a/src/main/java/org/wamblee/photos/model/plumbing/AuthorizedPhotos.java b/src/main/java/org/wamblee/photos/model/plumbing/AuthorizedPhotos.java new file mode 100644 index 0000000..935a79e --- /dev/null +++ b/src/main/java/org/wamblee/photos/model/plumbing/AuthorizedPhotos.java @@ -0,0 +1,31 @@ +/* + * 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. +} diff --git a/src/main/java/org/wamblee/photos/model/plumbing/Configuration.java b/src/main/java/org/wamblee/photos/model/plumbing/Configuration.java new file mode 100644 index 0000000..e84c5d4 --- /dev/null +++ b/src/main/java/org/wamblee/photos/model/plumbing/Configuration.java @@ -0,0 +1,51 @@ +/* + * 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; + } + +} diff --git a/src/main/java/org/wamblee/photos/model/plumbing/Eager.java b/src/main/java/org/wamblee/photos/model/plumbing/Eager.java new file mode 100644 index 0000000..0c081a2 --- /dev/null +++ b/src/main/java/org/wamblee/photos/model/plumbing/Eager.java @@ -0,0 +1,28 @@ +/* + * 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. +} diff --git a/src/main/java/org/wamblee/photos/model/plumbing/EagerExtension.java b/src/main/java/org/wamblee/photos/model/plumbing/EagerExtension.java new file mode 100644 index 0000000..e3fee74 --- /dev/null +++ b/src/main/java/org/wamblee/photos/model/plumbing/EagerExtension.java @@ -0,0 +1,74 @@ +/* + * 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> eagerBeansList = new ArrayList>(); + + public void collect(@Observes ProcessBean 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 diff --git a/src/main/java/org/wamblee/photos/model/plumbing/Initializer.java b/src/main/java/org/wamblee/photos/model/plumbing/Initializer.java new file mode 100644 index 0000000..2509641 --- /dev/null +++ b/src/main/java/org/wamblee/photos/model/plumbing/Initializer.java @@ -0,0 +1,48 @@ +/* + * 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(); + } +} diff --git a/src/main/java/org/wamblee/photos/model/plumbing/PhotoApp.java b/src/main/java/org/wamblee/photos/model/plumbing/PhotoApp.java new file mode 100644 index 0000000..2ba0a6d --- /dev/null +++ b/src/main/java/org/wamblee/photos/model/plumbing/PhotoApp.java @@ -0,0 +1,31 @@ +/* + * 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. +} diff --git a/src/main/java/org/wamblee/photos/model/plumbing/Producer.java b/src/main/java/org/wamblee/photos/model/plumbing/Producer.java new file mode 100644 index 0000000..9ad3c14 --- /dev/null +++ b/src/main/java/org/wamblee/photos/model/plumbing/Producer.java @@ -0,0 +1,172 @@ +/* + * 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 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> photoCache = new EhCache>( + 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 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); + } + +} diff --git a/src/main/java/org/wamblee/photos/security/AuthenticationFilter.java b/src/main/java/org/wamblee/photos/security/AuthenticationFilter.java new file mode 100644 index 0000000..04442f2 --- /dev/null +++ b/src/main/java/org/wamblee/photos/security/AuthenticationFilter.java @@ -0,0 +1,84 @@ +/* + * 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. + } + +} diff --git a/src/main/java/org/wamblee/photos/security/PhotoAuthorizationRule.java b/src/main/java/org/wamblee/photos/security/PhotoAuthorizationRule.java new file mode 100644 index 0000000..0fab5c5 --- /dev/null +++ b/src/main/java/org/wamblee/photos/security/PhotoAuthorizationRule.java @@ -0,0 +1,103 @@ +/* + * 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 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()"; + } +} diff --git a/src/main/java/org/wamblee/photos/utils/JpegUtils.java b/src/main/java/org/wamblee/photos/utils/JpegUtils.java new file mode 100644 index 0000000..2b0a0e7 --- /dev/null +++ b/src/main/java/org/wamblee/photos/utils/JpegUtils.java @@ -0,0 +1,106 @@ +/* + * 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); + } + +} diff --git a/src/main/java/org/wamblee/photos/wicket/BasePage.html b/src/main/java/org/wamblee/photos/wicket/BasePage.html new file mode 100644 index 0000000..18506e3 --- /dev/null +++ b/src/main/java/org/wamblee/photos/wicket/BasePage.html @@ -0,0 +1,37 @@ + + + + Title goes here + + + + +
+ Logout +
+ + + + +
+
+
+ +
+ +
+ + + diff --git a/src/main/java/org/wamblee/photos/wicket/BasePage.java b/src/main/java/org/wamblee/photos/wicket/BasePage.java new file mode 100644 index 0000000..d4bdc11 --- /dev/null +++ b/src/main/java/org/wamblee/photos/wicket/BasePage.java @@ -0,0 +1,75 @@ +/* + * 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; + } +} diff --git a/src/main/java/org/wamblee/photos/wicket/ErrorPage.html b/src/main/java/org/wamblee/photos/wicket/ErrorPage.html new file mode 100644 index 0000000..b767bf1 --- /dev/null +++ b/src/main/java/org/wamblee/photos/wicket/ErrorPage.html @@ -0,0 +1,18 @@ + + +Wicket Quickstart Archetype Homepage + + +Wicket Quickstart Archetype Homepage +
+
+ + + +

The page could not be displayed

+ +
+ + + diff --git a/src/main/java/org/wamblee/photos/wicket/ErrorPage.java b/src/main/java/org/wamblee/photos/wicket/ErrorPage.java new file mode 100644 index 0000000..c60d418 --- /dev/null +++ b/src/main/java/org/wamblee/photos/wicket/ErrorPage.java @@ -0,0 +1,36 @@ +/* + * 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(); + } +} diff --git a/src/main/java/org/wamblee/photos/wicket/HomePage.html b/src/main/java/org/wamblee/photos/wicket/HomePage.html new file mode 100644 index 0000000..2e7ccfe --- /dev/null +++ b/src/main/java/org/wamblee/photos/wicket/HomePage.html @@ -0,0 +1,18 @@ + + +Wicket Quickstart Archetype Homepage + + +Wicket Quickstart Archetype Homepage +
+
+ + + + Message here. + + + + + diff --git a/src/main/java/org/wamblee/photos/wicket/HomePage.java b/src/main/java/org/wamblee/photos/wicket/HomePage.java new file mode 100644 index 0000000..a6126b7 --- /dev/null +++ b/src/main/java/org/wamblee/photos/wicket/HomePage.java @@ -0,0 +1,72 @@ +/* + * 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 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()); + } + } +} diff --git a/src/main/java/org/wamblee/photos/wicket/WicketApplication.java b/src/main/java/org/wamblee/photos/wicket/WicketApplication.java new file mode 100644 index 0000000..8339000 --- /dev/null +++ b/src/main/java/org/wamblee/photos/wicket/WicketApplication.java @@ -0,0 +1,72 @@ +/* + * 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 getHomePage() { + return HomePage.class; + } + +} diff --git a/src/main/java/org/wamblee/photos/wicket/banner.png b/src/main/java/org/wamblee/photos/wicket/banner.png new file mode 100644 index 0000000000000000000000000000000000000000..1098af2ad2c215c4ed0fc98085a0ce0b08e4434e GIT binary patch literal 210 zcmeAS@N?(olHy`uVBq!ia0vp^j6gh%gAGW^mpv*12^0spJ29*~C-V}>VN3FMcVYMs zf(!O8p9~b?EbxddW?8bi$x3$Ffo4rm0E-(F9>RpzP&oe{)!5a5Sl!9 zznns!nQt~Z36Lv;7$of**01(0|Ex16#$oQqEseC4b+xkTB zJ`~t1cVvZ7!fvO^&tEE@{OPzVT_N7;*NqYP7a|Qs z9_VM5X-m$yV)#Y+f13!d(p)h>i5|!Orx(B^^(-1;Lx4Joz_mkQ`j+x1b{m^K)8hTr zO%AL-arUD5e`k@QTpKe;GFAE|@ITmG{;b@)*f^88N)_BlAL989dXvQM^e}NwqNs>^ zC3;<#9so*0Lb`6pk}rFt@X~fkw>*d>N3+km)+tDD$9d$znt6X+sXsZpw&F?F=H9H*5WwJw8~_QRx2l`dB0$QV0`D5UsaYU zK;WKc?f59-vRIU(-`&+zos3{NyY>9m?{cMM7=Q&v6;T3E%fOM5ky+T+{Wy-b=@^oamlL@nKtt$FMl$FI_(mOYVToZychBc6|y! zc(jR182BAx!MoILV@Io5wDk2E1$$vny6+b&w^QCm-a_r7amAQ{k+&p6Y$x8oSYVoT zq8`i+Ea#0LZu=z3tTi96@A^XoOE5h*1W#6Uia3rmckPYN&=ael5iRCLYISJ0B4GmW z&F)QSb>q_Jgi_~p#Wb}|R$dRb`;7docap?T1g6VGvwG_WSV=mz)4&XQzrIrd3h~=p z2E1OX+b8ArPW0Jcy^D%)>VrigO|W;DEg|IFO)SY9>EtTu}i>-iH>LHlxtjsC?-?iyk! z?f5-cg!m)#uZq+5FQ(JxT=7OZ@Am}o(0U08!Ptc0eLx(T*qMygn{Aw6j?i8F&f|Q{ z?@=76T%Y&qqDB13tTVeSnL6ZuqjGg~g_pA;`cfTUcYI5q(q5u$)4Z$7I_Whj{r7B_ zV^?%KavkL}Aq5VHSYx0dMiN{oTv$JEA-84b*SQH@)?8c|H6rC|)Q4#vc0<>$ls7MYRX;s@NAZ@wcH_c3U0H@Rgk$ksW_ zBE-S7@e^p2=_`gj*0>|AS?}zLMJo8N9)3H2dIQik{^aNP%q=l;+ke49{t)gETwtag zh*SarrVqFTvdR@PdWIuE zSpTs4l3Zk*A_1MG6IPNaWPOigAU=Wb7q)$S9-aDIIS+ z2<9n`A&}^zvb2v{RaJpzRl6u<{Bl|KW}yVG2RH!WmOV#9KZFTmXCgt# z0nPx{anp%>_NNCr6`ua)>Ez#uU)Le9Xs4UBU_WYF%EG zv!|wZ?BKkgTL*8+DFa%Rh=vD16_B5ebL%*kIFLBq#|sUDOROlK4w~jJefArQ#mzFvev~fDEvaQOx|K75tRh2CM-bDKAGGBRj2s(&AejIOfD?SCS zO=G}QL%^f`r-+9m3y2anf!>`13=;yp2hal0K&ium_=VGhTnNd-{wm^R&9A=1VZDoq zw#&tF&Hdy;yO+D8o#f)WF0$hPy3ThqRGJVUeY**1LR}b#!5O}zrW@+II2T=;AUXgJ z`>FH51J?UJ9ZEo*cKp^8&(@P`egXPi8s70Q{BL>B{$qvsiq(X=F) zHd5A&KU;KoPC1TC%@dI}p(1jbf8O@)l%nbrhbKXC_H*Co5-C{*3BKLfhXFr(=FY=Wj&4pINYtnWn+Ux5w7eI9 zhq?CM#7rRY&tb76A)bS)dA2nd7N9@Z64HpC$U-yni`*pXkBN=T)nthM`pwKt~ zOsoQ^LmqBls?(rSOmuG(G^!_HPTsG$Wlgeg*9_N*{8Cx*vOC$EVVv=4l)~%$N!YGs z6U=e4cKlsLa03ei2`7$mO{0SP5J0;}TE4ep51PQBMBTV@{Y2%wWyG-jNx0g@;u<-E;1AlD* zNt-~6A1N=XExVg^PY#hOmBsqufU`@SCQAi2<##-+tqkXj<7oRgYb_l3)}AG$QL9`{ z6~hNMhv`c#rxQurEi3QLf2GE?Pw_>9F>M#1k0+fJ<06-6Ren#7$x6jokpr;`CD<_x zu}UQ{fq}an+6YXsu;_4PT2j<}7A-goM)Gp@Un|AI+10rJ_e zHH?O@@CWnNh8$BvyS@mq<2Irz%lhc|@fTi*A}5EUhibAHM40hUX_bk^{wFVj2WOmE zk%4sT64dBCJYd@_2wRo}D{u=<2?@TL9im@BcgeeG7z)v$FYoRS&Ut24qJk{*1!0Bt8LME(MVRT7YA`dtLm?z04{3B`(=FHR zC5v@Hq~wF*?Pms}+O^|JOVezte{%h0`bk6==rqGwKC@^fah~iK;5+-hDZGE=FqI&E z7eHeaK3mJSFD9TdRn@A+tSzEu=w8+Ox!UZr(Y9`Cpm&$kHqh z&j#3fNT~=fGEs0?Qgzn^xq9*mb4s#Mp`7ASYF>>I0+IV@NXT5bvb3GF3IJu1{`<2F1^#Qg6QRdak z0_u?rzXK=2*O%e(MKW80_gMa6_d5jKml)+=yk>S~{vnBcZFNP)^5$17YBkdZXjJ-< zmlhBZ5+y?%oU!0IWiHmkO@|_@14o!GUfCh1s1VF`WK&|;!=8T#zQ60W;vx0my~ui! zY4P6{lMOUDN`sNShIa6z#S9qE1WkI!UwWE{vmYz64?>8LKgl>HU299SpXrRDcHVs3 zX&9ComQE6X3d92KgpIoDdma9+>NpuDME=XrT+v!VyW@OdNr~iuvv-Nr{3`28N*60} z=1f`1zO@_BOLMH!|2*Oz;OqN?_K1AI!CojrQSEQqV_GIK09wWrwm%0P7X$Y5)@BRl z`rkt?qk&w?(AvPcC`Sn-iTOCuD{8b*_fmy1Jk*1=Xc;rEDNeM7#vf+G$(y~npWv{& z>RHDh1Sn0|(95RrB5<5yX;4UwT3=|rH&?s~&1thPg~W1oK_NmXxo2Q4f_%-SEYer; zGod|}JcSLNRp&}}{mQ=(FseTri4Qb3zTCNso)m&YWiJdfWB)=4OeX~&FCtWW6aKm; z%1s5U5!IR!_qM>Lj#2TDU4D7xPq+RkO^tofiZRwQ@L?qq1?RQ}k40aAvj{~ zwWY=+>ccz_>F+WUNBZJ=(_Sd;|Ri zY=*J#Md)W}!8y(PAK?+_|4DXtmExPAh4sO__5jO#0XEB@Jy6IKu;SG@c}@H!G=6x_ zAxIR#E&WElnxTO?e+s}Wfoc%5roj`TMV@vLSz-`|0F|fPA`cDAD8EKg;eP1ziko?YR4M4?Do!;W8YrFHNTy@(!&5wU3wLp>KpW}E*o zv(Hr_YzxJX0m{%~?cj!a23O#KzwCHR_2Co?q0!{EH+Gb1KxEmtSzR`c*F1VeO_-%x zUdtX!v$IdH0H+ty)o{+8ky02@2iwqqf#gX?4AuwLMK>r(TrH3aMyv-lt2i6`T`kEK zCm}*EON(<3%ai#Th9n)N!5obHNd&u017*_UU7hyG?`Uc2Fqwj|x3t(& zU+9){O0LsrV2m1dM`Br904I1K;);Sw znFMcr2qIfzC=g9&pQdd@)?&TuT}D3U;;dC5BJ{<6)5(dGDu@Y9JZ5hK7xzi9?qPFu zy5iUAp3x{IIrFYPR)j_|8u`Y@wu&<)pfu&#hBgTfj(xx`6h59BMM?-V$fWX*e7aI7KR`w7AVuVRG+S@l&)uXuXI3@VO=v!yJMp!HOZ~{(fQwL7JL!8G%)inRQ=H5G<4I>52L+ zEq8@*A(8{iCysu!j-JX(i387q=P!-YOWOBP)fZds>Kx`m4Qzxk)jD&Rs-1fVqoDOa zJ+cb~ofQ~0KBkhv{lJpb(L{4XO|O=6X9tJ?XcLAc?0SEwmTf{dO*e;-A6o@+ZyRfE z6EE-ePFtSmzJE=3!5X`$Kj+rLS2loWCJ}%AQk;nWS>NgdL+xQ^;WvR) zW#!(G%WJUVdc0UptYvW}u&r=b9CkRy>JgsEl*mE*WubLaF!dNYceZReJrB~-JR;U4 zcr_i$qoaaY9rF*zvp^cxfD z$0jzIMD8zXcmQ@6kMi5zoaSjqN*b28Sw`3=jgx~bw)B#4qA_l*ronk1J4CzWkT_Hc zMlxA4EcUP;Q2(6(@R1s_OKq{e4J{5-fVYg}F=%xgddE1; z7zi$_6scA~W>f*vAW}xC!*)tdJTgHt^J>j4SmW2m(G8UATP?S?!;jI@pjNX0IbGnF zzXmLut@6c|`C1k5m2Zk(%ZeKHo-o8;p=j%4-HJu(u>95}ql^iagyH9*$~G)B^`it? zWifxeMzr;KGRVL{!Y?;Q zIK9hw4;%qA)hcH3&OCV5WhAAj0J`Zn&ZJGxnFQWtfR@Qq^1op={Ebb@>#6DJbGf1H zV#}UJ`G~~GDlE|YGimlFuvZT$Xx})l7P2e?;nio)vm~}zzF`ILuQ+GuvribCGO-S?6oQd zld3X3*5HA#_zuDmsod;QLkMTFIGNZJaU3tJPzv!J@d493R5oOdw!+w7g|U_zY)NLV zX19r}FDf8T_aWXSv4Tm`)c(cA^n|7+vh@YLaDyzYlkXjK4|u|R}^0AfMPRjOssG+J&0*aiYD{fSxmn_`gX4EwBd%na5|~-_`{y*AaK`)2}@;;Sad6hMNH!!_%=im8aF6=B-L8 z%Vw>xBy)AEyzEjHdkyACc$7~C36#R6B@F{Dq`s(Id@!_3^SQW}7js~p;X?nUvoeLo zcoL?z{SGZgq0c0Cmja8=!9v`^qlWefuGRwW;3y70z!@LvdZReJNw+7*Bzv--UqD?*cog0s=3M`>Cz zygW=;q^E&A*$jTL_0TS@7)p~919zg5D57MH)7?_@uBMZeRT`3@!yBZEp{oWaQ$6tm+#NeWBj#V6_Puf*)uXO z9k{Ox6YH9jz$f{XHWCYin$~if6)JLkTjyxYW$?}gI=fErNeV>U+uIAS2b}#3^j*O} zG^Ax$fWd#HQgwP>@M;F8OENLEs`zyeZ zbsQ>7Wnj3m2iXO-P$_?+3ZmF_S|#E)-@wtUVO?nua=li@(773`HUny*o6ceVv`)pH zpPWqnD1seYY(4lz$%Z*qfOK?F1t$0I;0WF;6xRCC)rx4UHU$q;dj5BE?x?Iw^&z{d zR{$*9j_~UR8)CY^R7eY}Ps&6zjqR>~GU2O`s6t`JpD-LXvEvK2kpkm{!{LMnrMxqZ zdIRc21v(`a70C9Os6cAQg=dlpGEKKk_XKu~40g;wSNgK*T`wJ>=jve>FDPu&byey2 zIz>DVXy+4ErN+s9x$=hd2$EF=KzwjaraWq+r>Mm~ z{szoWwTQJdgJ38?pGQgEw^J9Mk)UfpwvLf{VrnTuntBtDnybH!aqjL;V9s_u+-EfL zwB+wJuBmL{Kvn47kS~ETiyXA#eHnB`oV~Xavvi z(kRE?{?Dteh!v4tg28hoyc8Hih0bBsWMKQooIs!j&AXNN4CU^KG7b+z!UkJEJlLgb zupSol!`IH3ljs9=n)I6=Xg7G|$ebkk<6U znl4cVO8-M8>WlRq8T>-7+LHXF?k#7|H3qZb$+d9>mmU=xidf)_CC=o-j*LataD$ws+_jrzBSdK{JJCl1_ zLhkSJCwPyGQ*4{MoELO(a!5SsA4kkJc?xmDWX${;vESz&3cX!#d^vJ$&G3yqTp!Mx ztap4L+;u9|st|D50L;+|eJ%N^7S6j`_S>lvsfL7>015H&XCo3^YO%{Gv7Uc6u=eDw z2ughURNFP{Jf4cP}bf@8n)5T|TR3gmK2AP%NsccDY=7jIvd%GzIvKs#o!HNNgAM9#lMIBacM zvyb*>zgb;kkRGAgu3zgD#GcH5*L!rB{kOaDofGw-TYl<&juGR>?{FceRYCU}(uyMz zy?=upBF}Yobtl%>W3sc!U0hr?7R__|8f}+3-@o@au0xvU`ONgC)_fgkL!r-%8`FA1 zQCkep62&B7L3Ktq16NY)X-3mq7;Mr?_2oMIL$0|h0#G+S3yI+ARx7{&Yb?A0o--+7 z7QQU15-||>0^jY7P|#hX>b~j^F=C5Uy-Nv$Qo?*8(C2n@JdNSkcLumYic@XN^_S>x z=%d()a)UaSnd4|^j9SLUb*R(SdHs7X+QRe&W@m}F!7UZOb(cw%P z6SneS6nAz~w70htzIj5}n{@-^Jssa4IRyp7tr{ZIxx=aic!O8FVR`*3i_jvH7np$Z z7i#s+U5PSw@%*{CE~-DKhy#VK@-hvT6r|EBzs4H7)QBt%LhwPW-yy5tB~~1|=T+#q z>mUmK$^}S%b?2=iEBh6fr6czPgs;3f%-0vJ@e`639sV@zm}H$^Qb6^ zuUVRBzroS~Tcg4oNN#I$zyUldaKGZ`l`ELfkq6}u57uytkyw!)hQ*xZY~EZ;U|ad6;kmLrWc%$)Kaa5=Aa? z0K@{me?I!>AgtFKB zxxR$?TOF3f5FIpY3X<-GPi#SEeUmc_L0y7@2Zx6}6BAf&M+^Uh0vj8&H(*Y-%+syN z*Da&u*X$j$06Ue@i}xHyAzagwIR)D{`UOeoqgi*D1R6 zDo5b*jT(G5$;v7^TB`(hS67(qs!SdLK#})fF2FUS*t8u2$L>-XU|hTtNw2AsKvdKq zRoqVuB^Z?Lv4xC=iTNgXNj_Xn>(W^(M!e$M6zYZbiCqSBo>hLMYTk`>qr}piVP~_c z@Gl(2iN~l#8uSoNEF~+hqWndcpx!GSlIo{U65?v{G^jXewjP#`ocoiY%@06G2sk`M zv>sL%qsp8tQ9`EUqF}Wt_gOd+@ggoU+|*^IS7_*+{;O@dxHmdQXV|uID~UlGo03fq zalqn4E0SySB@Yye{z4WN)*8IjVoxaMck(ZqGR@H6hA-UpaPI3sG+An9rgV!&@<;Cm z;__Xb{y+3?--JJh)58c;KEMAmDTo>XMVd%_sCZxunt$re;wVs}qhc&16B}2(C3N)Y z=eek6?nK4VbKIV(Z0CBP-9pKWCNF_;}4@uS@;U7>!o`Xthu+D_uwZERHcO=Gr z*yvJLT6Ht!M0QdJ+o1k(M(vFr#o-LtQL6J7grM#Ppx*>uyMvjk-2ridVim{+mEsyI z9s@e5%N@_Zg0qCb%v-^pF$Zbg8<_nH`XYCW)^M(F6(hSFTGP;2Q#HPolZ^R?ZI;l8 z*at&(*&-&=j&(8MS_W| z)TmL)Gt8DhoOE+$Wy}n8%QH2)kaUCcwjq0KJu$^ciACh<&otIEC62NOWEZkCI$!$<~~Z- z+K(W?hD{v0eN-1guLc@DP3cjQR(!6|h^v`9lQiLoIFB;<(R|>}ii$s3`R`?q)ev1g=Cv2z^ zEVeRbO$g>%sxu}jq^6I2Mf_xJGLG{u0iBEI-yZiH#5(GZ&nwJXK zzPIKCp&L>!+!G#Xp4Xo#CAa;~{dt`G@f>8T@Y*Vj!^9~Q7O*VbY)6PJzE;hXc+ZfL zXh`BInUW$atCe|8Fbq{Ra$D8)U1@pAI*mKOfww>Loe%iwb$0&SYotkIxtI!@nRD)7fh|2!S zY%b*s0q>&SxUdHhg$pj{mSyY;v>-RJ8m|8MvrWA6h1i{SQi;fEcAp@>dZjnq={;l8 zpag`cj6kUV8JU3`v#w;`D{hM7R1?nGY@7TpTy7ravjJY6Rk~bD*LU_LGd{A6diKVI zqpcQ-6*xtoGkEn4eoa*CTcujw@pbo}F>z_}WOh+R68;yoI^CA%P3@ag@2aJiOk^2}%UIY)bO)JUPGoQ_=Qeef>V*@Tqg zFYIC_jz)Do3mxBtw{Z8=wUwZ0GWH0!)kc&(0KnxMJ3VC;&Ja;ZV6aUb0S@Y5dK`8?) z<7}4}ERelwRlC3>Aq@kVHSJ@|G#!Tx6n*}oexwsImtN5W=#O*T{nD3aEQ;GXhj3J4 zqMUyb_=tcDIHN0dcR`nHp}Z*sF(3>Opzyhq!&0jktNHXT@Q`4-3Lk`uPe9wq=8qi- zBqM`8-Af1^djrjL$k1+P4wav6vCfVWwYG|{hfTARh3^(uq5ZoUJm5Mv8ZE2W5dM)R zCXAUxQ8^9gJ?29GTwNyBU$&h)fkEB8(qwl>e}^b|h5BX<>NvALc@X*09jH>ssm2I% z;tcJ}!EJy{aFo$?On=rd$2(*E{PxEw(-?c#iKy5z-b^n_P6A1CHb39&tHCI~HN9f9 z_4UumlG@9G4r9O78M`*x-!C3NfBk~bMSv3h`%~f;!7M<&C$o9^%)rs+J5Tdy-X00h z`P-N-oA+v}PFWceiUDu_!MnPBD>OADk3FtM%hw4+p>IU)DgmIzaXB-E`KkEZ(6o%$8P#BVJ}Ew(dsBCFHIT z@47Eo97=3~`$6umbmjfcuS8a+3ey3HwJDXRt~8Mz6P;=xv|d4>VwpO7C(IC6a4Qu< zc5nV$xUIgOpFF0ZE)RPHV)s$9N=s^nC!x_cM<&2tbqo_#f*)1KRciXA3q8c0=(59X2K4{ymN0*;YN?cIWZJ6590qA>FeV ztwwBfXD8F6&`F^RAu1Yy1f-eG{0>uY{(E-*p+rVPr>%7o;CxB^TPc>7C?++;l-b|A zX(Zu?mL%LT|BPf0lq04!tJrrrJ7lb@d{obhN8!ge_;SxfeY*KpPr64}Y!+8kID{Ni zL==j}E1aM-1vC&c9UP0y?CE|`lefDVm^lTaTWqwc&s4yvDZ!r7^X9M9Gcd%B?|v@R z*s)MomhBCLg%K)c2~kXXa#@|h4> z5*6y(muiF{QPQTn3uEeHe7ruZtWA_K?Y#5q6>6aExHc#IR&sX=+ z-9l8p>DP*R)y9Q`5&XjQz2sAc1-@Ki=eK;5>}G`$VQl6Zy0cUe$aMwli*?z`)VsUW zO?)0Ma<~}F2UkP?u;g7me)pZ&LtoEjRhk9Czq?wh`$9~}GFk%(rFL#9x}TMzqKvaO zZ;t7?iOoXTBgIzBEt+yKxLZVv&G7qmna5#}PnCu5ROgBS4uEG1 zuOMkiS7LNxzb?_fT0PfiPqn~!Ds&~C``#ISl~^-az157EFB7D%vc=r7%y!UW=sMbl z`T37%s%{d?6Zn#^?Sq3vW`tGKq#{`_8xmzoZj08~jntH(K`IF)#8vo-S!;@B`yZSa zc9Bro$dzasBXAs ztlA%pBT6*6vOAQ*tFfK+zmYB5iChc5ssFwjI0 zRI7~Ac>{1&_g-+PMhl!Y>|~e%j%MD``a%bVWY}k3v*QD1l(xwujOCqiImPf-&NQzD z^x4(e5rOgJiqJ&GX~cpGO=|yoy8V_ggu(*6cUGV29+iEm>0^V`8DuPc_jE?ZOX(Ha zo{sSLO2q5B zA+>7N^-ppd=EAaf?K`+lHP*$}>Wf=_iNKsb8EsHN;(ILx5QhUiai5>)8Fj9dDtE;7 z=vwM+nGTmX7N{X4WCrpmo3zreau}q{^))a~>Y|(KFoHQ(WXno~G__~ZG zjTJpxOKPni3;4>XV=D_U(I4@{owvlvZ+d}nffBj<=91!scYms{-j*^U?p9XTzncAC zyKV#5mAAC2_3Y2hE*8i_k>E37%)=zBwwO55JMZ_M1A5PZABfy*ClQry*lL^>JDB-y zlQ#3g#+oPF(M<8vI1SyjQ=zy1Bq1xZ6BD1=lYqmL(;dfz)99&-_~zzDwqBzt!k-P{ zTYeJ^{gM;;S6_AUojPIGevbW#x>qv=1aOLZl|6f9|7t9L_HO6n*5Q>RZhJweh!pOu zX4Fwqqgf57^==}C<6M~YSaW|JWQX%2`r09bkO-QN6(*3oiE@e+0iWh15s&yY1_khupAzDQ}c5Oqupir(Z=y;xFF=#b4%ib?7c@sDgwn-Rg4nAh;It#1tNpCKLeN*Hc7=x&Q-0M5{Yx;^umFdz(6w?}0UT5Sq%_`?RO6R22Dr-)v z3`)(3W?3`AjZ)wsIQ1<-`*0aD|4m#1I{#(3p|{wj?Uz6n0t^0g{>P^BHZiW<)_=+H zZMC=pmm?5bRGSC4?I1|;>a}hww_uS*R-zN{9>XyxIO5%i5GuoI*E<7A133BVrzwmv z(;<<-tH;%+VBO>sdaCdMUc*+5)k^9lm3tCruD|>4E^ZRjJR)`N)pDcOyP7cuaF^WK z)@3eIj+*dUD~Q`l+HvqhklN@&(l7DWNtW;)qZBvW7Qt6?P!`>H(5^&)A)O~X7qr6`Eh68 zg$?;>wyvoXhE6B7$iZRd(lW2@Uv%1rZ5ktyMtPJ%$# zLRTw0Cnwd!?d9{lsg}WA{nJUyKsCn7y2D)ygNta#Po#-v4HqS$`GP4f$P_TfTeMt1 z1|)!{n*ILCXEMiNsq?5^HV_`zuekU-J-(&W&NivjM9842t0EqgCVebtpnvBqRI&^4 zO?4|{mdZtb(Ilk|#}@NJ=6Oe~MVfO26WQL0bgp4AyXQ74<`F8f5f(yrSRh;4oS=TB zC}ip2-H!n-qf|{8oAgvLZgk&wqYZDr+6HtC$da8P(;;${v9@&mus3KiKgP$kesO7y zwf`r~AzXhw8Grc~9~xvrW(*zppnv$4g=#Gy7bKW!^SJd=VxN5{6|))klahb+Y;>0I zJr7^Nn3$f{0X@zIPJ+}6R^&sn)O6w@5+@(~`oLznzg$v!qFWOlc6SZo>EO<8dLA>c zI{QBa(G!Ie(ecFOFXS2?99+9fb~%ovDN`w%0ii5C%WYrn)E2L;h(g$9^PH4hwOaGM zACV=I5^0dl;o`_iK?6y*7+0>fkhylT?P}8pnuI<6`(T1sdDqR&xGHOU?o~fB*XTc+ z?+6h&xAZYU0y}zWQJ_IV38EM?``_gb2S&nLnV|&;l#FdP_11;2N99CAvedAP-Q0vv z$#LZtGk;GxeW0b3lf0BUm*zwo%w2p4n&!64P0V(}o_bt0JijZX@Hkgj%f_*rU)7ri zLNT*jE&NfK84olKNO|8?`0zO*6!5^)5TD08>L&<~Kj;adCZfCJZAN4i$9^CAl_HX% zGUu7g1#M=^u30&MR^BT2drc^poR%lpn7@BsCmVJ1(+8WhN={X91x4mNGYQ5B?`9J! z^B7#VE1ZxqjCq4Gs()`Xf{>sYzP}!CYK(Mk1rqIBM;eK&En-C}FA45^df-D*@s$(i zO-wq*@KIdTT=?1}XI7Uc8vXmVi6+b$Nj_W0u55`lIY(~P9FfP(Jq@NTsdamviGthd z%jwF%F%N#3U>P_WCQKR}^hEQwd^Gz!dL&IY91%C(fBlCMeR)^mrTEIv9e^;$e&vUg z*YMxZy$u#(e?d>5>|Qkw5x<~o6#Nx#!C9K+v@J#X<7IPtQ{2_0Z%F()_&#zif}v^s zQI;$)ghm5l%!mB;#btH%z+?29tt@IQFjHAX>Bx1y zx%LH*@He1v*)!q z7bR6p?%9eZzjmSjnE&aOw&^r-Jek2A0Nb2A_Pg4rsMD)G?l{ED8wneiYKOnRt?Oenv9t}=+kB2$;+9{Q2a3+Vj<)3ju?h`^QVPIPx3?eN>0i; z7f1PVgzb$2UUX2FL!;?+A&Q09tKo+h*-H5Hu=JnPz5T8&qcI{je%lMU9rL8qEDU|d zxgBD)7SbUung%wO=Om>obNb~hqMw(EGuN}xV~VuwJ@TKQ+WC$Az_$92RW4RTc<;CsZqw3!~I(*dS~zq4Z_2VWBws)C@{kt|APieWYly9{A@7(^4t1kR z8lub&qaptBmI6tcrj)Or{Il(59u9+;$=r<~=R|0hf5jt?`NULB)B0~GpU~$=sKE1e z=-)C~Bkb>tuNZ(G3_GtyaeFP>Ex}u5KRYAe#omwRZLfu=tcTtw$r`_Ywf!cUnFKc4 zW@KjJIRd6<>Ga@~xSLfYTNf>*DfKuTE!_u3Xw8@C*NGDEvBMGar>`1KIFSU3O@}S}PqcZqim|=hNNA~-#G8Z* zrc{mlDKV^`+YRd;PO{6E6#e5Jexs)%AqS-`d0xwye9iP7u4hu=LwM-sNLf|X(cE&| zEMd;*`pvn>7{Tfpbs893*TpQzg34glkimRMi!vPIm-8=-RcVOZTS!-Oy%k=wJjy5h zNIC(MlDSa1T10~?RxKdT0deKBvm}rksKkAIiqh5^G&% zCdhmL8RnQ2V)j^N1{_WXj*k9ma^;H$!EQ)xS|55S2s<5?;Gp7aTlNn-nYzwbtPYjb zW1kqJ$(@)XX*@Kn8+PHX53HX)z?Xi@eU2~+2wGw7Zufz_&5avsZluoQo9dqIIBkG)5ZIeb4-3c^<0uHz@!)B{8r4hdqW8o4 zFNOv>eMWQI@9&p=`R@HZea^YXgD<{+xw{>Im?8c7zCNBO^JCQ8`cpZ*OJu6>HFd!PUh0qRoTUjc}Hj7JtvYKm(&_#)l|HRs8fDeQW0@ut0LLu>xE>$@%Pnx}pA46iW( zinmmMUoD>)i~03NzMA%1w<`N1Q&fC7xD{(WTDSm8V8)`h!!4+=T0dxDTGWXmM#7A% z#`=%>A1!OEn${dbI?e;LL)^L1^_R~`u?0##29zqja2dhtjh&o+sqsl}sQAuZn>sPA z;-C)_fL+F;;dPK#D@D}2Pf^V}--lKELNTx1ltqR2CPr8&G zpH&J}=|#^U&b`+3?{YH*so8q%9}}#O&5wx$N=?v?Y!9J6#J}1VPw+6Q z1?xMi+d4W#`oqgV4wTne>^&vni)!@H+znV(R62e8`RwSs7wQXsdH8|$~V4tRrshs+=fZ# zG)%05_kP<2R29_!>F?v;v7Nh!|8{*j7G)l(&V?n`?hr(L3Jp zxd$$gHd81gJE3r*LbPp@bsRpzcKfO zAPX*$Wh#kpS*nGu`a>(yLk7`0%y(uIFJ{oJ!mV=Oa4}Z6t2xk;21Smi?>fLl*$uP+-p~L9MxQsw?7XO)!cTxX zjSYdc6fvOyWVS&Ynu8lhLrI?%#|E^)wAc58q5z!eT?dLt;h3V#M{D2;+mNup&fp~r z@F~VeJSE0rydgT+T;vz!Pn*ShfT+N6DmuZ?=uCW3!U1rKA%##O-Y_KGJsHSXryFf+iX{w}ey8M8>Vck00btlV9}akJeDWVOwRt2SRuLxKrLn6=aQ@i0Ra79zfv^5;15uC z-uyd*3+4LVcXzOoP;e^#^|Rt_Qv!GE*9tM5>_Sz;c2e3zRY_#J{~t|(!i#wSlC!Jd UE3EA8EI&a0ql$F3BqZSf09B|c6#xJL literal 0 HcmV?d00001 diff --git a/src/main/webapp/WEB-INF/beans.xml b/src/main/webapp/WEB-INF/beans.xml new file mode 100644 index 0000000..82ec09d --- /dev/null +++ b/src/main/webapp/WEB-INF/beans.xml @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..6762478 --- /dev/null +++ b/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,98 @@ + + + + cdi + + + + + authentication + org.wamblee.photos.security.AuthenticationFilter + + loginpage + /login.jsp + + + + role + ALL + + + + resources + /resources + + + + + photos + org.apache.wicket.protocol.http.WicketFilter + + applicationClassName + org.wamblee.photos.wicket.WicketApplication + + + configuration + development + + + + + authentication + /* + + + photos + /* + + + + + resources + /resources/* + + + + + securedaccess + /* + + + ALL + users + + + + + FORM + PhotoXChangeRealm + + /login.jsp + /loginError.jsp + + + + + 10 + + + + login.jsp + + + + ALL + + + diff --git a/src/main/webapp/login.jsp b/src/main/webapp/login.jsp new file mode 100644 index 0000000..e3fbb11 --- /dev/null +++ b/src/main/webapp/login.jsp @@ -0,0 +1,56 @@ +<%@ page language="java" pageEncoding="UTF-8" session="true"%> + + + +<% + String path = request.getContextPath(); + String basePath = request.getScheme() + "://" + + request.getServerName() + ":" + request.getServerPort() + path + + "/"; +%> + + + + + +Home Page + + + + + + + + + + + Please login: +
+ + + + + + + + + + + + + +
Username: +
Password: +
+
+
+ + + diff --git a/src/main/webapp/loginError.jsp b/src/main/webapp/loginError.jsp new file mode 100644 index 0000000..0791b49 --- /dev/null +++ b/src/main/webapp/loginError.jsp @@ -0,0 +1,17 @@ + +<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> +<% +String path = request.getContextPath(); +String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; +%> + + + + + + + + Wrong user id or password specified.
+ + + diff --git a/src/main/webapp/logout.jsp b/src/main/webapp/logout.jsp new file mode 100644 index 0000000..2421ffe --- /dev/null +++ b/src/main/webapp/logout.jsp @@ -0,0 +1,6 @@ + + +<% + session.invalidate(); + response.sendRedirect( "" ); +%> \ No newline at end of file diff --git a/todo.txt b/todo.txt new file mode 100644 index 0000000..35231be --- /dev/null +++ b/todo.txt @@ -0,0 +1,97 @@ + +- 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 | + + + -- 2.31.1