/*
* Copyright (c) 2006-2009 by Dirk Riehle, http://dirkriehle.com
*
* This file is part of the Wahlzeit photo rating application.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/>.
*/
package org.wahlzeit.model;
import com.google.appengine.api.images.Image;
import com.googlecode.objectify.ObjectifyService;
import com.googlecode.objectify.Work;
import org.wahlzeit.model.persistence.ImageStorage;
import org.wahlzeit.services.LogBuilder;
import org.wahlzeit.services.ObjectManager;
import org.wahlzeit.services.Persistent;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
/**
* A photo manager provides access to and manages photos.
*/
public class PhotoManager extends ObjectManager {
/**
*
*/
protected static final PhotoManager instance = new PhotoManager();
private static final Logger log = Logger.getLogger(PhotoManager.class.getName());
/**
* In-memory cache for photos
*/
protected Map<PhotoId, Photo> photoCache = new HashMap<PhotoId, Photo>();
/**
*
*/
protected PhotoTagCollector photoTagCollector = null;
/**
*
*/
public PhotoManager() {
photoTagCollector = PhotoFactory.getInstance().createPhotoTagCollector();
}
/**
*
*/
public static final PhotoManager getInstance() {
return instance;
}
/**
*
*/
public final boolean hasPhoto(String id) {
return hasPhoto(PhotoId.getIdFromString(id));
}
/**
*
*/
public final boolean hasPhoto(PhotoId id) {
return getPhoto(id) != null;
}
/**
*
*/
public final Photo getPhoto(PhotoId id) {
return instance.getPhotoFromId(id);
}
/**
*
*/
public Photo getPhotoFromId(PhotoId id) {
if (id == null) {
return null;
}
Photo result = doGetPhotoFromId(id);
if (result == null) {
result = PhotoFactory.getInstance().loadPhoto(id);
if (result != null) {
doAddPhoto(result);
}
}
return result;
}
/**
* @methodtype get
* @methodproperties primitive
*/
protected Photo doGetPhotoFromId(PhotoId id) {
return photoCache.get(id);
}
/**
* @methodtype command
* @methodproperties primitive
*/
protected void doAddPhoto(Photo myPhoto) {
photoCache.put(myPhoto.getId(), myPhoto);
}
/**
* @methodtype get
*/
public final Photo getPhoto(String id) {
return getPhoto(PhotoId.getIdFromString(id));
}
/**
* @methodtype init Loads all Photos from the Datastore and holds them in the cache
*/
public void init() {
loadPhotos();
}
/**
* @methodtype command
*
* Load all persisted photos. Executed when Wahlzeit is restarted.
*/
public void loadPhotos() {
Collection<Photo> existingPhotos = ObjectifyService.run(new Work<Collection<Photo>>() {
@Override
public Collection<Photo> run() {
Collection<Photo> existingPhotos = new ArrayList<Photo>();
readObjects(existingPhotos, Photo.class);
return existingPhotos;
}
});
for (Photo photo : existingPhotos) {
if (!doHasPhoto(photo.getId())) {
log.config(LogBuilder.createSystemMessage().
addParameter("Load Photo with ID", photo.getIdAsString()).toString());
loadScaledImages(photo);
doAddPhoto(photo);
} else {
log.config(LogBuilder.createSystemMessage().
addParameter("Already loaded Photo", photo.getIdAsString()).toString());
}
}
log.info(LogBuilder.createSystemMessage().addMessage("All photos loaded.").toString());
}
/**
* @methodtype boolean-query
* @methodproperty primitive
*/
protected boolean doHasPhoto(PhotoId id) {
return photoCache.containsKey(id);
}
/**
* @methodtype command
*
* Loads all scaled Images of this Photo from Google Cloud Storage
*/
protected void loadScaledImages(Photo photo) {
String photoIdAsString = photo.getId().asString();
ImageStorage imageStorage = ImageStorage.getInstance();
for (PhotoSize photoSize : PhotoSize.values()) {
log.config(LogBuilder.createSystemMessage().
addAction("loading image").
addParameter("image size", photoSize.asString()).
addParameter("photo ID", photoIdAsString).toString());
if (imageStorage.doesImageExist(photoIdAsString, photoSize.asInt())) {
try {
Serializable rawImage = imageStorage.readImage(photoIdAsString, photoSize.asInt());
if (rawImage != null && rawImage instanceof Image) {
photo.setImage(photoSize, (Image) rawImage);
}
} catch (IOException e) {
log.warning(LogBuilder.createSystemMessage().
addParameter("size", photoSize.asString()).
addParameter("photo ID", photoIdAsString).
addException("Could not load image although it exists", e).toString());
}
} else {
log.config(LogBuilder.createSystemMessage().
addParameter("Size does not exist", photoSize.asString()).toString());
}
}
}
/**
*
*/
public void savePhoto(Photo photo) {
updateObject(photo);
}
@Override
protected void updateDependents(Persistent obj) {
if (obj instanceof Photo) {
Photo photo = (Photo) obj;
saveScaledImages(photo);
updateTags(photo);
UserManager userManager = UserManager.getInstance();
Client owner = userManager.getClientById(photo.getOwnerId());
userManager.saveClient(owner);
}
}
/**
* @methodtype helper
*/
public List<Tag> addTagsThatMatchCondition(List<Tag> tags, String condition) {
readObjects(tags, Tag.class, Tag.TEXT, condition);
return tags;
}
/**
* @methodtype command
*
* Persists all available sizes of the Photo. If one size exceeds the limit of the persistence layer, e.g. > 1MB for
* the Datastore, it is simply not persisted.
*/
protected void saveScaledImages(Photo photo) {
String photoIdAsString = photo.getId().asString();
ImageStorage imageStorage = ImageStorage.getInstance();
PhotoSize photoSize;
int it = 0;
boolean moreSizesExist = true;
do{
photoSize = PhotoSize.values()[it];
it++;
Image image = photo.getImage(photoSize);
if (image != null) {
try {
if (!imageStorage.doesImageExist(photoIdAsString, photoSize.asInt())) {
imageStorage.writeImage(image, photoIdAsString, photoSize.asInt());
}
} catch (Exception e) {
log.warning(LogBuilder.createSystemMessage().
addException("Problem when storing image", e).toString());
moreSizesExist = false;
}
} else {
log.config(LogBuilder.createSystemMessage().
addParameter("No image for size", photoSize.asString()).toString());
moreSizesExist = false;
}
} while (it < PhotoSize.values().length && moreSizesExist);
}
/**
* Removes all tags of the Photo (obj) in the datastore that have been removed by the user and adds all new tags of
* the photo to the datastore.
*/
protected void updateTags(Photo photo) {
// delete all existing tags, for the case that some have been removed
deleteObjects(Tag.class, Tag.PHOTO_ID, photo.getId().asString());
// add all current tags to the datastore
Set<String> tags = new HashSet<String>();
photoTagCollector.collect(tags, photo);
for (Iterator<String> i = tags.iterator(); i.hasNext(); ) {
Tag tag = new Tag(i.next(), photo.getId().asString());
log.config(LogBuilder.createSystemMessage().addParameter("Writing Tag", tag.asString()).toString());
writeObject(tag);
}
}
/**
*
*/
public void savePhotos() throws IOException{
updateObjects(photoCache.values());
}
/**
* @methodtype get
*/
public Map<PhotoId, Photo> getPhotoCache() {
return photoCache;
}
/**
*
*/
public Set<Photo> findPhotosByOwner(String ownerName) {
Set<Photo> result = new HashSet<Photo>();
readObjects(result, Photo.class, Photo.OWNER_ID, ownerName);
for (Iterator<Photo> i = result.iterator(); i.hasNext(); ) {
doAddPhoto(i.next());
}
return result;
}
/**
*
*/
public Photo getVisiblePhoto(PhotoFilter filter) {
filter.generateDisplayablePhotoIds();
return getPhotoFromId(filter.getRandomDisplayablePhotoId());
}
/**
*
*/
public Photo createPhoto(String filename, Image uploadedImage) throws Exception {
PhotoId id = PhotoId.getNextId();
Photo result = PhotoUtil.createPhoto(filename, id, uploadedImage);
addPhoto(result);
return result;
}
/**
* @methodtype command
*/
public void addPhoto(Photo photo) throws IOException {
PhotoId id = photo.getId();
assertIsNewPhoto(id);
doAddPhoto(photo);
GlobalsManager.getInstance().saveGlobals();
}
/**
* @methodtype assertion
*/
protected void assertIsNewPhoto(PhotoId id) {
if (hasPhoto(id)) {
throw new IllegalStateException("Photo already exists!");
}
}
}