From: Erik Brakkee Date: Wed, 25 Sep 2013 19:33:24 +0000 (+0200) Subject: Working upload of photos (individual and zip) X-Git-Url: http://wamblee.org/gitweb/?a=commitdiff_plain;h=387bef406fe4b276473d4063c4d227fce6ff2e11;p=photos Working upload of photos (individual and zip) Now streaming the images instead of reading all data when constructing the page. --- diff --git a/src/main/java/org/wamblee/photos/model/authorization/AuthorizedAlbum.java b/src/main/java/org/wamblee/photos/model/authorization/AuthorizedAlbum.java index 48fb47a..f76fd09 100644 --- a/src/main/java/org/wamblee/photos/model/authorization/AuthorizedAlbum.java +++ b/src/main/java/org/wamblee/photos/model/authorization/AuthorizedAlbum.java @@ -48,8 +48,7 @@ import org.wamblee.security.authorization.WriteOperation; @AuthorizedPhotos public class AuthorizedAlbum extends AuthorizedPhotoEntry implements Album { - private static final Logger LOGGER = Logger.getLogger(AuthorizedAlbum.class - .getName()); + private static final Logger LOGGER = Logger.getLogger(AuthorizedAlbum.class.getName()); private AuthorizationService _authorizer; @@ -57,37 +56,29 @@ public class AuthorizedAlbum extends AuthorizedPhotoEntry implements Album { private HttpSession _session; - protected AuthorizedAlbum() { - super(null); - // for CDI - } - /** * Constructs concurrent album as a decorator for an album implementation. - * - * @param aAlbum - * Album to decorate. + * + * @param aAlbum Album to decorate. */ @Inject - public AuthorizedAlbum(@AllPhotos Album aAlbum, - AuthorizationService aService, - @PhotoCache Cache> aCache, - HttpSession aSession) { + public AuthorizedAlbum(@AllPhotos Album aAlbum, AuthorizationService aService, + @PhotoCache Cache> aCache, HttpSession aSession) { super(aAlbum); _authorizer = aService; - _authorizedEntries = new CachedObject>( - aCache, aSession.getId() + "/" + aAlbum.getPath(), - new CachedObject.Computation>() { - public ArrayList getObject(String aObjectKey) { - return AuthorizedAlbum.this.compute(); - } - }); + _authorizedEntries = new CachedObject>(aCache, + "session:" + aSession.getId() + "/" + aAlbum.getPath(), + new CachedObject.Computation>() { + public ArrayList getObject(String aObjectKey) { + return AuthorizedAlbum.this.compute(); + } + }); _session = aSession; } /** * 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() { @@ -100,6 +91,9 @@ public class AuthorizedAlbum extends AuthorizedPhotoEntry implements Album { // automatically. } } + if (result == null) { + throw new RuntimeException("Result is null"); + } return result; } @@ -110,9 +104,8 @@ public class AuthorizedAlbum extends AuthorizedPhotoEntry implements Album { /** * Creates a decorate for the photo entry to make it safe for concurrent * access. - * - * @param aEntry - * Entry to decorate + * + * @param aEntry Entry to decorate * @return Decorated photo. */ private T decorate(T aEntry) { @@ -121,11 +114,9 @@ public class AuthorizedAlbum extends AuthorizedPhotoEntry implements Album { } 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(), _session); + return (T) new AuthorizedAlbum((Album) aEntry, _authorizer, _authorizedEntries.getCache(), _session); } else { - throw new IllegalArgumentException( - "Entry is neither a photo nor an album: " + aEntry); + throw new IllegalArgumentException("Entry is neither a photo nor an album: " + aEntry); } } @@ -157,7 +148,7 @@ public class AuthorizedAlbum extends AuthorizedPhotoEntry implements Album { } else { if (!(entry instanceof Album)) { throw new IllegalArgumentException(getPath() + " " + - aPath); + aPath); } return ((Album) entry).getEntry(remainder); } @@ -192,8 +183,13 @@ public class AuthorizedAlbum extends AuthorizedPhotoEntry implements Album { */ public void addImage(String aId, InputStream aImage) throws IOException { _authorizer.check(this, new WriteOperation()); + int oldsize = _authorizedEntries.get().size(); _authorizedEntries.invalidate(); decorated().addImage(aId, aImage); + int newsize = _authorizedEntries.get().size(); + if (newsize != oldsize + 1) { + throw new RuntimeException("cache was not refreshed property"); + } } /* @@ -214,16 +210,14 @@ public class AuthorizedAlbum extends AuthorizedPhotoEntry implements Album { */ public void removeEntry(String aId) throws IOException { // Check whether deletion is allowed. - PhotoEntry entry = _authorizer.check(decorated().getEntry("/" + aId), - new DeleteOperation()); + 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())) { + while (entry != null && !_authorizer.isAllowed(entry, new AllOperation())) { entry = decorated().findPhotoBefore(entry.getId()); } return decorate(entry); @@ -231,8 +225,7 @@ public class AuthorizedAlbum extends AuthorizedPhotoEntry implements Album { public Photo findPhotoAfter(String aId) { Photo entry = decorated().findPhotoAfter(aId); - while (entry != null && - !_authorizer.isAllowed(entry, new AllOperation())) { + while (entry != null && !_authorizer.isAllowed(entry, new AllOperation())) { entry = decorated().findPhotoAfter(entry.getId()); } return decorate(entry); diff --git a/src/main/java/org/wamblee/photos/model/filesystem/FileSystemAlbum.java b/src/main/java/org/wamblee/photos/model/filesystem/FileSystemAlbum.java index c08288b..fcf4695 100644 --- a/src/main/java/org/wamblee/photos/model/filesystem/FileSystemAlbum.java +++ b/src/main/java/org/wamblee/photos/model/filesystem/FileSystemAlbum.java @@ -129,7 +129,7 @@ public class FileSystemAlbum implements Album { } _dir = aDir; _path = aPath; - _entries = new CachedObject>(aCache, aPath, new AlbumComputation(this)); + _entries = new CachedObject>(aCache, "fs:" + aPath, new AlbumComputation(this)); } /** diff --git a/src/main/java/org/wamblee/photos/servlet/ImageSender.java b/src/main/java/org/wamblee/photos/servlet/ImageSender.java new file mode 100644 index 0000000..a734184 --- /dev/null +++ b/src/main/java/org/wamblee/photos/servlet/ImageSender.java @@ -0,0 +1,147 @@ +/* + * 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.servlet; + +import java.io.IOException; +import java.io.InputStream; + +import javax.inject.Inject; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.wamblee.photos.model.Album; +import org.wamblee.photos.model.Photo; +import org.wamblee.photos.model.PhotoEntry; +import org.wamblee.photos.model.plumbing.AuthorizedPhotos; + +/** + * Sends an image (either thumbnail or full size) from the photo album. + *

+ * The returned picture is defined based on + * {@link javax.servlet.http.HttpServletRequest#getPathInfo}. as follows. If the + * path info starts with the string defined by {@link #THUMBNAIL_NAME}, then a + * thumbnail image is served and the rest of the path info is the relative path + * of the picture. Otherwise the path info is identical to the relative path of + * the picture. + */ +public class ImageSender extends HttpServlet { + + @Inject + @AuthorizedPhotos + private Album root; + + static final long serialVersionUID = 4069997717483260853L; + + enum ImageType { + thumbnail, + photo, + resource + } + + /* + * (non-Javadoc) + * + * @see + * javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest + * , javax.servlet.http.HttpServletResponse) + */ + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + doPost(request, response); + } + + /* + * (non-Javadoc) + * + * @see + * javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest + * , javax.servlet.http.HttpServletResponse) + */ + protected void doPost(HttpServletRequest aRequest, HttpServletResponse aResponse) + throws ServletException, IOException { + + String entryPath = aRequest.getPathInfo(); + if (entryPath == null) { + entryPath = "/"; + } + + ImageType type = null; + for (ImageType t : ImageType.values()) { + if (entryPath.startsWith("/" + t + "/")) { + entryPath = entryPath.substring(t.toString().length() + 1); + type = t; + break; + } + } + + if (type == null) { + throw new RuntimeException("unsupported URL " + aRequest.getPathInfo()); + } + + switch (type) { + case thumbnail: + case photo: { + if (entryPath.endsWith(".jpg")) { + entryPath = entryPath.substring(0, entryPath.length() - ".jpg".length()); + } + PhotoEntry entry = root.getEntry(entryPath); + + Photo photo = (Photo) entry; + InputStream is = null; + if (type == ImageType.thumbnail) { + is = photo.getThumbNail(); + } else { + is = photo.getPhoto(); + } + sendImage(aResponse, is); + break; + } + case resource: { + try (InputStream is = getServletContext().getResourceAsStream("/images/" + entryPath)) { + ServletOutputStream os = aResponse.getOutputStream(); + byte[] buffer = new byte[4096]; + int n; + while ((n = is.read(buffer)) > 0) { + os.write(buffer, 0, n); + } + } + break; + } + + default: { + //throw new RuntimeException("Unknown type " + type); + } + } + } + + /** + * Sends the image. + * + * @param response Response + * @param is Input stream of the image. + * @throws IOException In case an IO problem occurs. + */ + + private void sendImage(HttpServletResponse response, InputStream is) throws IOException { + int c; + while ((c = is.read()) >= 0) { + response.getOutputStream().write(c); + } + } +} diff --git a/src/main/java/org/wamblee/photos/wicket/AlbumPanel.html b/src/main/java/org/wamblee/photos/wicket/AlbumPanel.html index 0df1af4..0786ab4 100644 --- a/src/main/java/org/wamblee/photos/wicket/AlbumPanel.html +++ b/src/main/java/org/wamblee/photos/wicket/AlbumPanel.html @@ -48,6 +48,9 @@ +

+ +
diff --git a/src/main/java/org/wamblee/photos/wicket/AlbumPanel.java b/src/main/java/org/wamblee/photos/wicket/AlbumPanel.java index 0c7841c..70fc097 100644 --- a/src/main/java/org/wamblee/photos/wicket/AlbumPanel.java +++ b/src/main/java/org/wamblee/photos/wicket/AlbumPanel.java @@ -18,17 +18,20 @@ package org.wamblee.photos.wicket; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.Serializable; import java.util.logging.Logger; import javax.inject.Inject; +import javax.servlet.ServletContext; import org.apache.wicket.PageParameters; +import org.apache.wicket.markup.ComponentTag; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.image.Image; import org.apache.wicket.markup.html.link.Link; import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.markup.repeater.RepeatingView; -import org.apache.wicket.resource.ByteArrayResource; +import org.wamblee.general.ValueHolder; import org.wamblee.photos.model.Album; import org.wamblee.photos.model.Photo; import org.wamblee.photos.model.PhotoEntry; @@ -39,6 +42,16 @@ import org.wamblee.photos.model.plumbing.AuthorizedPhotos; */ public class AlbumPanel extends Panel { + public static class MyValueHolder extends ValueHolder implements Serializable { + public MyValueHolder(T aValue) { + super(aValue); + } + + public MyValueHolder() { + super(); + } + } + private static final Logger LOGGER = Logger.getLogger(AlbumPanel.class.getName()); private static final long serialVersionUID = 1L; @@ -49,6 +62,9 @@ public class AlbumPanel extends Panel { @AuthorizedPhotos private transient Album authorized; + @Inject + private ServletContext context; + private class SerializableEntryLink extends Link { private String path; @@ -60,7 +76,6 @@ public class AlbumPanel extends Panel { @Override public void onClick() { - System.out.println("Entry " + path + " was clicked"); PageParameters pars = new PageParameters(); pars.put("path", path); @@ -98,19 +113,7 @@ public class AlbumPanel extends Panel { index = 0; } - PhotoEntry current = authorized.getEntry(path); - if (current instanceof Photo) { - throw new RuntimeException("AlbumPanel can only show album: " + current.getClass().getName()); - } - final Album album = (Album) current; - Link prevLink = new Link("prevLink") { - { - if (index - MAX_ROWS * MAX_COLUMNS < 0) { - setEnabled(false); - } - } - @Override public void onClick() { PageParameters pars = new PageParameters(); @@ -118,19 +121,18 @@ public class AlbumPanel extends Panel { pars.put("index", index - MAX_ROWS * MAX_COLUMNS); setResponsePage(HomePage.class, pars); } + + @Override + public boolean isEnabled() { + return index - MAX_ROWS * MAX_COLUMNS >= 0; + } }; add(prevLink); // Avoid implicit references to the album to keep the link objects // small and serializable. - final int albumSize = album.size(); + final int albumSize = getAlbum().size(); Link nextLink = new Link("nextLink") { - { - if (index + MAX_ROWS * MAX_COLUMNS >= albumSize) { - setEnabled(false); - } - } - @Override public void onClick() { PageParameters pars = new PageParameters(); @@ -138,6 +140,11 @@ public class AlbumPanel extends Panel { pars.put("index", index + MAX_ROWS * MAX_COLUMNS); setResponsePage(HomePage.class, pars); } + + @Override + public boolean isEnabled() { + return index + MAX_ROWS * MAX_COLUMNS < albumSize; + } }; add(nextLink); @@ -164,6 +171,7 @@ public class AlbumPanel extends Panel { RepeatingView pageLinks = new RepeatingView("pageLinks"); add(pageLinks); + Album album = getAlbum(); for (int i = 0; i < album.size() / MAX_ROWS / MAX_COLUMNS; i++) { final int istart = i * MAX_ROWS * MAX_COLUMNS; Link pageLink = new Link("pageLink") { @@ -188,34 +196,91 @@ public class AlbumPanel extends Panel { pageLinks.add(container); } - int ientry = index; - int irow = 0; - RepeatingView row = new RepeatingView("row"); - add(row); - while (irow < MAX_ROWS && ientry < album.size()) { - int icolumn = 0; - WebMarkupContainer columns = new WebMarkupContainer(row.newChildId()); - row.add(columns); - RepeatingView column = new RepeatingView("column"); - columns.add(column); - while (icolumn < MAX_COLUMNS && ientry < album.size()) { - WebMarkupContainer thumbnail = new WebMarkupContainer(column.newChildId()); - column.add(thumbnail); - - final PhotoEntry entry = album.getEntry(ientry); - Link link = new SerializableEntryLink("thumbnail", entry.getPath()); - thumbnail.add(link); - ImageData data = getData(entry); - - // TODO very inefficient. all data is loaded when generating the page. - link.add(new Image("image", new ByteArrayResource(data.getContentType(), data.getData()))); - - link.add(new Label("name", album.getEntry(ientry).getId())); - icolumn++; - ientry++; + RepeatingView row = new RepeatingView("row") { + @Override + protected void onPopulate() { + removeAll(); + final ValueHolder ientry = new MyValueHolder(index); + int irow = 0; + Album album = getAlbum(); + while (irow < MAX_ROWS && ientry.getValue() < album.size()) { + WebMarkupContainer columns = new WebMarkupContainer(newChildId()); + add(columns); + RepeatingView column = new RepeatingView("column") { + @Override + protected void onPopulate() { + removeAll(); + int icolumn = 0; + Album album = getAlbum(); + while (icolumn < MAX_COLUMNS && ientry.getValue() < album.size()) { + WebMarkupContainer thumbnail = new WebMarkupContainer(newChildId()); + add(thumbnail); + + final PhotoEntry entry = album.getEntry(ientry.getValue()); + Link link = new SerializableEntryLink("thumbnail", entry.getPath()); + thumbnail.add(link); + //ImageData data = getData(entry); + + // TODO very inefficient. all data is loaded when generating the page. + //link.add(new Image("image", + // new ByteArrayResource(data.getContentType(), data.getData()))); + + //link.add(new Image("image", + // new ContextRelativeResource("image/thumbnail" + entry.getPath()))); + + //final String url = "/image/thumbnail/" + entry.getPath(); + + if (entry instanceof Photo) { + link.add(new Image("image") { + @Override + protected void onComponentTag(ComponentTag tag) { + tag.put("src", + context.getContextPath() + "/image/thumbnail/" + entry.getPath()); + } + }); + } else { + link.add(new Image("image") { + @Override + protected void onComponentTag(ComponentTag tag) { + //tag.put("src", context.getContextPath() + "/resources/folder.png" + + // entry.getPath()); + tag.put("src", context.getContextPath() + "/image/resource/folder.png"); + } + }); + } + + link.add(new Label("name", album.getEntry(ientry.getValue()).getId())); + icolumn++; + ientry.setValue(ientry.getValue() + 1); + } + } + }; + columns.add(column); + irow++; + } } - irow++; + }; + + add(row); + + // upload panel + if (path.equals("/")) + + { + add(new WebMarkupContainer("uploadPanel")); + } else + + { + add(new UploadPanel("uploadPanel", path)); + } + } + + private Album getAlbum() { + PhotoEntry current = authorized.getEntry(path); + if (current instanceof Photo) { + throw new RuntimeException("AlbumPanel can only show album: " + current.getClass().getName()); } + return (Album) current; } public static final class ImageData { diff --git a/src/main/java/org/wamblee/photos/wicket/HomePage.java b/src/main/java/org/wamblee/photos/wicket/HomePage.java index 95290d3..4a34bc0 100644 --- a/src/main/java/org/wamblee/photos/wicket/HomePage.java +++ b/src/main/java/org/wamblee/photos/wicket/HomePage.java @@ -65,7 +65,6 @@ public class HomePage extends BasePage { @Override public void onClick() { - System.out.println("Entry " + path + " was clicked"); PageParameters pars = new PageParameters(); pars.put("path", path); setResponsePage(HomePage.class, pars); diff --git a/src/main/java/org/wamblee/photos/wicket/PhotoPanel.java b/src/main/java/org/wamblee/photos/wicket/PhotoPanel.java index a0fb7c1..e47b19a 100644 --- a/src/main/java/org/wamblee/photos/wicket/PhotoPanel.java +++ b/src/main/java/org/wamblee/photos/wicket/PhotoPanel.java @@ -64,30 +64,12 @@ public class PhotoPanel extends Panel { } add(new Label("path", path)); - PhotoEntry current = authorized.getEntry(path); - if (current instanceof Album) { - throw new RuntimeException("PhotoPanel can only show a photo: " + current.getClass().getName()); - } - final Photo photo = (Photo) current; - - String parentPath_ = path.substring(0, path.lastIndexOf("/")); - if (parentPath_.length() == 0) { - parentPath_ = "/"; - } - final String parentPath = parentPath_; - final Album parent = (Album) authorized.getEntry(parentPath); - final Photo before = parent.findPhotoBefore(photo.getId()); - final Photo after = parent.findPhotoAfter(photo.getId()); + String parentPath = getParentPath(); Link prevLink = new Link("prevLink") { - { - if (before == null) { - setEnabled(false); - } - } - @Override public void onClick() { + Photo before = getPrevPhoto(); if (before == null) { return; } @@ -95,19 +77,19 @@ public class PhotoPanel extends Panel { pars.put("path", before.getPath()); setResponsePage(HomePage.class, pars); } + + @Override + public boolean isEnabled() { + return getPrevPhoto() != null; + } }; add(prevLink); Link nextLink = new Link("nextLink") { - { - if (after == null) { - setEnabled(false); - } - } - @Override public void onClick() { + Photo after = getNextPhoto(); if (after == null) { return; } @@ -115,6 +97,11 @@ public class PhotoPanel extends Panel { pars.put("path", after.getPath()); setResponsePage(HomePage.class, pars); } + + @Override + public boolean isEnabled() { + return getNextPhoto() != null; + } }; add(nextLink); @@ -130,7 +117,7 @@ public class PhotoPanel extends Panel { public void onClick() { PageParameters pars = new PageParameters(); - pars.put("path", parentPath); + pars.put("path", getParentPath()); pars.put("index", 0); setResponsePage(HomePage.class, pars); } @@ -138,10 +125,38 @@ public class PhotoPanel extends Panel { add(parentLink); - Image image = new Image("photo", new ByteArrayResource("image/jpeg", getData(photo))); + Image image = new Image("photo", new ByteArrayResource("image/jpeg", getData(getPhoto()))); add(image); } + private Photo getPhoto() { + PhotoEntry current = authorized.getEntry(path); + if (current instanceof Album) { + throw new RuntimeException("PhotoPanel can only show a photo: " + current.getClass().getName()); + } + return (Photo) current; + } + + private Photo getPrevPhoto() { + return getAlbum().findPhotoBefore(getPhoto().getId()); + } + + private Photo getNextPhoto() { + return getAlbum().findPhotoAfter(getPhoto().getId()); + } + + private Album getAlbum() { + return (Album) getAuthorizedPhotos().getEntry(getParentPath()); + } + + private String getParentPath() { + String parentPath = path.substring(0, path.lastIndexOf("/")); + if (parentPath.length() == 0) { + parentPath = "/"; + } + return parentPath; + } + private byte[] getData(Photo aPhoto) { try (InputStream is = aPhoto.getPhoto()) { return getBytes(is); @@ -162,4 +177,8 @@ public class PhotoPanel extends Panel { } return bos.toByteArray(); } + + private Album getAuthorizedPhotos() { + return authorized; + } } \ No newline at end of file diff --git a/src/main/java/org/wamblee/photos/wicket/UploadPanel.html b/src/main/java/org/wamblee/photos/wicket/UploadPanel.html new file mode 100644 index 0000000..773e257 --- /dev/null +++ b/src/main/java/org/wamblee/photos/wicket/UploadPanel.html @@ -0,0 +1,28 @@ + + + Wicket Quickstart Archetype Homepage + + + + +
+
+ Upload individual jpg photos or a zip of individual jpg photos. + + + + + + + +
+ + + +
+
+
+ + + diff --git a/src/main/java/org/wamblee/photos/wicket/UploadPanel.java b/src/main/java/org/wamblee/photos/wicket/UploadPanel.java new file mode 100644 index 0000000..8a0e8d4 --- /dev/null +++ b/src/main/java/org/wamblee/photos/wicket/UploadPanel.java @@ -0,0 +1,138 @@ +/* + * 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.wicket; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import javax.inject.Inject; + +import org.apache.wicket.markup.html.form.Form; +import org.apache.wicket.markup.html.form.upload.FileUpload; +import org.apache.wicket.markup.html.form.upload.FileUploadField; +import org.apache.wicket.markup.html.panel.Panel; +import org.apache.wicket.util.lang.Bytes; +import org.wamblee.photos.model.Album; +import org.wamblee.photos.model.plumbing.AuthorizedPhotos; + +/** + * Created with IntelliJ IDEA. + * User: erik + * Date: 9/23/13 + * Time: 8:33 PM + * To change this template use File | Settings | File Templates. + */ +public class UploadPanel extends Panel { + + private static final Logger LOGGER = Logger.getLogger(UploadPanel.class.getName()); + + /** + * Extension to use for JPEGs. + */ + private static final String JPG_EXTENSION = ".jpg"; + + /** + * Extension to use for ZIP files. + */ + private static final String ZIP_EXTENSION = ".zip"; + + @Inject + @AuthorizedPhotos + private transient Album _authorized; + + private String _path; + + /** + * Upload field. + */ + private FileUploadField _uploadField; + + public UploadPanel(String aId, String aPath) { + super(aId); + + _path = aPath; + + Form form = new Form("uploadForm") { + protected void onSubmit() { + final FileUpload upload = _uploadField.getFileUpload(); + + if (upload == null) { + return; + } + + try { + String filename = upload.getClientFileName(); + InputStream is = upload.getInputStream(); + + if (filename.trim().length() == 0) { + return; + } + Album album = (Album) _authorized.getEntry(_path); + if (filename.toLowerCase().endsWith(JPG_EXTENSION)) { + insertPhoto(album, is, filename); + } else if (filename.toLowerCase().endsWith(ZIP_EXTENSION)) { + + try (ZipInputStream zip = new ZipInputStream(is)) { + insertPhotosFromZipFile(album, zip); + } + } else { + warn("Skipping entry with unknown file type '" + filename + "'"); + } + } + catch (Exception e) { + LOGGER.log(Level.WARNING, e.getMessage(), e); + error("ERROR:" + e.getMessage()); + } + } + }; + add(form); + _uploadField = new FileUploadField("file"); + form.add(_uploadField); + form.setMultiPart(true); + form.setMaxSize(Bytes.megabytes(500)); + } + + private void insertPhotosFromZipFile(Album aAlbum, ZipInputStream aZipFile) throws IOException { + // zip extension + ZipEntry entry; + while ((entry = aZipFile.getNextEntry()) != null) { + try { + if (!entry.isDirectory() && entry.getName().toLowerCase().endsWith(JPG_EXTENSION)) { + insertPhoto(aAlbum, aZipFile, new File(entry.getName()).getName()); + } else { + warn("Skipping entry '" + entry.getName() + "' from zip file."); + } + } finally { + aZipFile.closeEntry(); + } + } + } + + private void insertPhoto(Album aAlbum, InputStream aPhotoInputStream, String aFilename) throws IOException { + String photoName = aFilename.substring(0, aFilename.length() - JPG_EXTENSION.length()); + + if (aAlbum.getEntry("/" + photoName) != null) { + error("Photo '" + photoName + "' already exists in this album"); + return; + } + aAlbum.addImage(photoName, aPhotoInputStream); + info("Photo '" + photoName + "' uploaded"); + } +} diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index eaa1fd7..96f4453 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -16,6 +16,21 @@ or "deployment". If no configuration is found, "development" is the default. --> + + + Servlet which sends the raw images. + ImageSender + ImageSender + org.wamblee.photos.servlet.ImageSender + 1 + + + ImageSender + /image/* + + + authentication org.wamblee.photos.security.AuthenticationFilter diff --git a/src/main/java/org/wamblee/photos/wicket/folder.png b/src/main/webapp/images/folder.png similarity index 100% rename from src/main/java/org/wamblee/photos/wicket/folder.png rename to src/main/webapp/images/folder.png diff --git a/src/test/java/org/wamblee/photos/model/AlbumTest.java b/src/test/java/org/wamblee/photos/model/AlbumTest.java index a0c4d07..5d4c7aa 100644 --- a/src/test/java/org/wamblee/photos/model/AlbumTest.java +++ b/src/test/java/org/wamblee/photos/model/AlbumTest.java @@ -40,9 +40,6 @@ import static org.junit.Assert.*; */ public class AlbumTest { - private static final String ADDED_METHOD = "photoAdded"; - private static final String REMOVED_METHOD = "photoRemoved"; - private static final String TEST_RESOURCES = "src/test/resources/albumdata"; private String resourcesPath; @@ -68,7 +65,6 @@ public class AlbumTest { assertTrue(path.startsWith("file:")); path = path.substring("file:".length()); resourcesPath = path + "../../" + TEST_RESOURCES; - System.out.println(resourcesPath); } private void copyDir(File aSource) { @@ -99,6 +95,11 @@ public class AlbumTest { return new File(_testData.getRoot(), "data"); } + protected Album createAlbum(File aDir, String aPath, Cache> aCache) + throws IOException { + return new FileSystemAlbum(aDir, aPath, aCache); + } + /** * Verifies that a non-existing album cannot be opened. */ @@ -106,7 +107,7 @@ public class AlbumTest { public void testNonExistingAlbum() { File dir = new File(_testData.getRoot(), "NonExisting"); try { - Album album = new FileSystemAlbum(dir, "", _cache); + Album album = createAlbum(dir, "", _cache); } catch (IOException e) { return; // ok @@ -124,7 +125,7 @@ public class AlbumTest { File dir = getTestOutputDir(); copyDir(_dir); - Album album = new FileSystemAlbum(dir, "/", _cache); + Album album = createAlbum(dir, "/", _cache); int nentries = album.size(); assertEquals(3, nentries); @@ -177,7 +178,7 @@ public class AlbumTest { File dir = getTestOutputDir(); copyDir(_dir); - Album album = new FileSystemAlbum(dir, "/", _cache); + Album album = createAlbum(dir, "/", _cache); assertEquals(0, album.size()); } @@ -190,7 +191,7 @@ public class AlbumTest { File dir = getTestOutputDir(); copyDir(_dir); - Album album = new FileSystemAlbum(dir, "/", _cache); + Album album = createAlbum(dir, "/", _cache); assertEquals(0, album.size()); } @@ -202,7 +203,7 @@ public class AlbumTest { File _dir = new File(getTestAlbumData(), "AlbumPhotoMissing"); File dir = getTestOutputDir(); copyDir(_dir); - Album album = new FileSystemAlbum(dir, "/", _cache); + Album album = createAlbum(dir, "/", _cache); assertEquals(0, album.size()); } @@ -222,13 +223,13 @@ public class AlbumTest { assertTrue(new File(dir, FileSystemAlbum.PHOTOS_DIR).mkdir()); assertTrue(new File(dir, FileSystemAlbum.THUMBNAILS_DIR).mkdir()); - Album album = new FileSystemAlbum(dir, "/", _cache); + Album album = createAlbum(dir, "/", _cache); File image = new File(getTestAlbumData(), "a.jpg"); album.addImage("xyz", getImage(image)); assertTrue(album.getEntry("/xyz") != null); - Album album2 = new FileSystemAlbum(dir, "/", new ForeverCache>()); + Album album2 = createAlbum(dir, "/", new ForeverCache>()); assertTrue(album2.getEntry("/xyz") != null); } @@ -243,7 +244,7 @@ public class AlbumTest { assertTrue(new File(dir, FileSystemAlbum.PHOTOS_DIR).mkdir()); assertTrue(new File(dir, FileSystemAlbum.THUMBNAILS_DIR).mkdir()); - Album album = new FileSystemAlbum(dir, "/", _cache); + Album album = createAlbum(dir, "/", _cache); File image = new File(getTestAlbumData(), "a.jpg"); album.addImage("xyz", getImage(image)); @@ -269,7 +270,7 @@ public class AlbumTest { assertTrue(new File(dir, FileSystemAlbum.PHOTOS_DIR).mkdir()); assertTrue(new File(dir, FileSystemAlbum.THUMBNAILS_DIR).mkdir()); - Album album = new FileSystemAlbum(dir, "/", _cache); + Album album = createAlbum(dir, "/", _cache); File sub = new File(dir, "sub"); album.addAlbum("sub"); @@ -278,7 +279,7 @@ public class AlbumTest { assertTrue(new File(sub, FileSystemAlbum.PHOTOS_DIR).isDirectory()); assertTrue(new File(sub, FileSystemAlbum.THUMBNAILS_DIR).isDirectory()); - Album album2 = new FileSystemAlbum(dir, "/", new ForeverCache>()); + Album album2 = createAlbum(dir, "/", new ForeverCache>()); PhotoEntry entry = album2.getEntry("/sub"); assertTrue(entry != null); @@ -296,7 +297,7 @@ public class AlbumTest { assertTrue(new File(dir, FileSystemAlbum.PHOTOS_DIR).mkdir()); assertTrue(new File(dir, FileSystemAlbum.THUMBNAILS_DIR).mkdir()); - Album album = new FileSystemAlbum(dir, "/", _cache); + Album album = createAlbum(dir, "/", _cache); File sub = new File(dir, "sub"); album.addAlbum("sub"); @@ -321,7 +322,7 @@ public class AlbumTest { File dir = getTestOutputDir(); File origDir = new File(getTestAlbumData(), "AlbumRemove"); copyDir(origDir); - Album album = new FileSystemAlbum(dir, "/", _cache); + Album album = createAlbum(dir, "/", _cache); try { album.removeEntry("Nested"); } diff --git a/src/test/java/org/wamblee/photos/model/AuthorizedAlbumTest.java b/src/test/java/org/wamblee/photos/model/AuthorizedAlbumTest.java new file mode 100644 index 0000000..549bf13 --- /dev/null +++ b/src/test/java/org/wamblee/photos/model/AuthorizedAlbumTest.java @@ -0,0 +1,62 @@ +/* + * 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.model; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import javax.servlet.http.HttpSession; + +import org.wamblee.cache.Cache; +import org.wamblee.cache.EhCache; +import org.wamblee.io.ClassPathResource; +import org.wamblee.photos.model.authorization.AuthorizedAlbum; +import org.wamblee.security.authorization.AuthorizationService; +import org.wamblee.security.authorization.Operation; +import static org.mockito.Mockito.*; + +/** + * Created with IntelliJ IDEA. + * User: erik + * Date: 9/23/13 + * Time: 9:08 PM + * To change this template use File | Settings | File Templates. + */ +public class AuthorizedAlbumTest extends AlbumTest { + + @Override + protected Album createAlbum(File aDir, String aPath, Cache> aCache) + throws IOException { + Album fileSystemAlbum = super.createAlbum(aDir, aPath, + aCache); //To change body of overridden methods use File | Settings | File Templates. + AuthorizationService service = mock(AuthorizationService.class); + when(service.isAllowed(anyObject(), any(Operation.class))).thenReturn(true); + HttpSession session = mock(HttpSession.class); + when(session.getId()).thenReturn("myid"); + Album authorized = new AuthorizedAlbum(fileSystemAlbum, service, createCache(), session); + return authorized; + } + + @Override + protected Cache createCache() { + try { + return new EhCache(new ClassPathResource("META-INF/ehcache.xml"), "test"); + } + catch (Exception e) { + throw new RuntimeException(e.getMessage(), e); + } + } +}