From: Erik Brakkee Date: Sat, 14 Sep 2013 16:25:23 +0000 (+0200) Subject: just before adding authorization service. X-Git-Url: http://wamblee.org/gitweb/?a=commitdiff_plain;h=8845e7fe6141ccc98fd070ee4e653941f6e60508;p=photos just before adding authorization service. --- 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 0000000..1098af2 Binary files /dev/null and b/src/main/java/org/wamblee/photos/wicket/banner.png differ diff --git a/src/main/java/org/wamblee/photos/wicket/photos-print.css b/src/main/java/org/wamblee/photos/wicket/photos-print.css new file mode 100644 index 0000000..e69de29 diff --git a/src/main/java/org/wamblee/photos/wicket/photos.css b/src/main/java/org/wamblee/photos/wicket/photos.css new file mode 100644 index 0000000..eaa78ec --- /dev/null +++ b/src/main/java/org/wamblee/photos/wicket/photos.css @@ -0,0 +1,110 @@ +/* general */ +body { + font-family: Arial, Helvetica, sans-serif; + list-style-type: square; + font-size: 20px; +} +/* menu styling */ + +#menu { + font-size: 20px; + clear: both; +} + +#menu ul { + list-style: none; + margin: 0; + padding: 0; + padding-top: 1em; +} + +#menu li { + display: inline; + background: black; + padding: 4px 10px 5px 10px; + color: white; +} + +#menu li a { + color: #82cafa; + text-decoration: none; +} +/* banner */ + +#banner { + background: url(banner.png); + height: 150px; + width: 100%; +} + +#banner img { + float: left; + margin: 0; + border: medium none; +} + +#banner .title { + display: block; + font-size: 16px; + position: absolute; + text-align: center; + top: 135px; + width: 178px; +} + +#logout { + display: block; + position: absolute; + top: 20px; + left: 220px; +} + +/* feedback panel */ + +ul.feedbackPanel { + color: red; +} +/* the contents on the main page */ + +#content { + clear: both; + margin-top: 1em; +} + +#content em { + display: block; + margin-bottom: 0px; + font-style: normal; + font-weight: bold; + font-size: 24px; +} + +#content table { + text-align: left; +} + +#content th, #content td { + vertical-align: top; + padding: 10px; +} + +.entries a { + display: block; + float: left; + font-size: 24px; + margin-left: 10%; + margin-right: 24px; + line-height: 34px; + width: 100%; +} + +.entries .index { + display: block; + float: left; + font-size: 30px; + width: 100%; + margin-top: 10px; +} + + + diff --git a/src/main/java/org/wamblee/photos/wicket/wamblee_logo.png b/src/main/java/org/wamblee/photos/wicket/wamblee_logo.png new file mode 100644 index 0000000..6407ed9 Binary files /dev/null and b/src/main/java/org/wamblee/photos/wicket/wamblee_logo.png differ 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 | + + +