/* Copyright (c) 2009 Google Inc. * * 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 com.google.appengine.demos.sticky.server; import java.util.ArrayList; import java.util.Date; import java.util.List; import javax.jdo.JDOHelper; import javax.jdo.JDOObjectNotFoundException; import javax.jdo.PersistenceManager; import javax.jdo.PersistenceManagerFactory; import javax.jdo.Transaction; import javax.jdo.annotations.Element; import javax.jdo.annotations.IdGeneratorStrategy; import javax.jdo.annotations.IdentityType; import javax.jdo.annotations.PersistenceCapable; import javax.jdo.annotations.Persistent; import javax.jdo.annotations.PrimaryKey; import com.google.appengine.api.datastore.DatastoreService; import com.google.appengine.api.datastore.Key; import com.google.appengine.api.users.User; /** * An application specific Api wrapper around the {@link DatastoreService}. * Creating a {@link Store} is relatively expensive so one should be mindful not * to create these unnecessarily. For example, in a servlet, the Store should be * stored as a field in the store to avoid creating it on every request. * * @author knorton@google.com (Kelly Norton) * */ public class Store { public class Api { /** * The JDO persistence manager used for all calls. */ private final PersistenceManager manager; private Api() { manager = factory.getPersistenceManager(); } /** * Begin a new transaction. * * @return the transaction */ public Transaction begin() { final Transaction tx = manager.currentTransaction(); tx.begin(); return tx; } /** * Close the connection to the data store. Clients are expected to guarantee * that close will be called. This will also rollback any active * transaction. */ public void close() { final Transaction tx = manager.currentTransaction(); if (tx.isActive()) { tx.rollback(); } manager.close(); } /** * Gets the author by email. * * @param email * the author's email * @return the author * @throws JDOObjectNotFoundException */ public Author getAuthor(String email) { return manager.getObjectById(Author.class, email); } /** * Gets a note from the data store. * * @param key * the note's key * @return */ public Note getNote(Key key) { return manager.getObjectById(Note.class, key); } /** * Looks in the data store for an author with a matching email. If the * author does not exist, a new one will be created. The newly created * author will also have access to a newly created surface. * * @param user * the user for which an author object is needed * @return an author object */ public Author getOrCreateNewAuthor(User user) { try { return getAuthor(user.getEmail()); } catch (JDOObjectNotFoundException e) { // If an author wasn't found, we create a new one and save it to the // store. // First, persist a default surface for the author. final Transaction txA = begin(); final Surface surface = new Surface("My First Surface"); surface.addAuthorName(user.getNickname()); saveSurface(surface); txA.commit(); // Then, create a new author based on the information in user. final Transaction txB = begin(); final Author author = new Author(user.getEmail(), user.getNickname()); author.addSurface(surface); saveAuthor(author); txB.commit(); return author; } } /** * Gets a surface from the data store. * * @param key * the surface's key * @return */ public Surface getSurface(Key key) { return manager.getObjectById(Surface.class, key); } /** * Persist an author to the data store. * * @param author * the author to be persisted * @return <code>author</code>, for call chaining */ public Author saveAuthor(Author author) { return manager.makePersistent(author); } /** * Persist a note to the data store. * * @param note * the note to be persisted * @return <code>note</code>, for call chaining */ public Note saveNote(Note note) { note.lastUpdatedAt = new Date(); return manager.makePersistent(note); } /** * Persist a surface to the data store. * * @param surface * the surface to be persisted * @return <code>surface</code>, for call chaining */ public Surface saveSurface(Surface surface) { // Update the last updated value before saving. surface.lastUpdatedAt = new Date(); return manager.makePersistent(surface); } /** * Attempts to get the author with the given email. If there is no known * author with that email, <code>null</code> will be returned. * * @param email * the author's email * @return the author or <code>null</code> if the author can't be found */ public Author tryGetAuthor(String email) { try { return getAuthor(email); } catch (JDOObjectNotFoundException e) { return null; } } } /** * An ORM object representing an author. */ @PersistenceCapable(identityType = IdentityType.APPLICATION) public static class Author { /** * The author's email. Serves as the primary key for this object. */ @PrimaryKey private String email; /** * The author's name. */ @Persistent private String name; /** * The keys of all surfaces this author has access to. */ @Persistent(defaultFetchGroup = "true") @Element(dependent = "true") private List<Key> surfaceKeys = new ArrayList<Key>(); /** * Construct a new author. * * @param email * the author's email * @param name * the author's name */ public Author(String email, String name) { this.name = name; setEmail(email); } /** * Give this author access to a surface. * * @param surface * the surface the author is being granted access to. */ public void addSurface(Surface surface) { final List<Key> keys = new ArrayList<Key>(surfaceKeys); keys.add(surface.getKey()); setSurfaceKeys(keys); } /** * Gets the author's email. * * @return */ public String getEmail() { return email; } /** * Gets the author's name. * * @return */ public String getName() { return name; } /** * Returns the keys for each surface that the author has access to. * * @return */ public List<Key> getSurfaceKeys() { return surfaceKeys; } /** * Returns whether this author has access to a particular surface. * * @param surfaceKey * the key of the surface * @return <code>true</code> if the author does have access, * <code>false</code> otherwise */ public boolean hasSurface(Key surfaceKey) { for (Key key : surfaceKeys) { if (key.equals(surfaceKey)) { return true; } } return false; } /** * Sets the author's email. * * @param email */ public void setEmail(String email) { this.email = email; } /** * Sets the author's name. * * @param name */ public void setName(String name) { this.name = name; } /** * Reassigns the collection of surface keys. This is required to ensure that * the ORM will persist element collections. * * @param keys */ private void setSurfaceKeys(List<Key> keys) { surfaceKeys = keys; } } /** * An ORM object representing a note. */ @PersistenceCapable(identityType = IdentityType.APPLICATION) public static class Note { /** * An auto-generated primary key for this object. This key will be a child * key of the owning surface's key. */ @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Key key; /** * The x position of the note. */ @Persistent private int x; /** * The y position of the note. */ @Persistent private int y; /** * The width of the note. * * <p> * NOTE: The application does not currently provide the ability to resize * notes. * </p> */ @Persistent private int width; /** * The height of the note * * <p> * NOTE: The application does not currently provide the ability to resize * notes. * </p> */ @Persistent private int height; /** * The text content of the note. */ @Persistent private String content; /** * The date of the last time this object was persisted. */ @Persistent private Date lastUpdatedAt = new Date(); /** * The email of the author created this note. */ @Persistent private String authorEmail; /** * The name of the author who created this note. */ @Persistent private String authorName; /** * Create a new note. * * @param owner * the author who created this note * @param x * @param y * @param width * @param height */ public Note(Author owner, int x, int y, int width, int height) { this.x = x; this.y = y; this.width = width; this.height = height; authorEmail = owner.getEmail(); authorName = owner.getName(); } /** * The author's email. * * @return */ public String getAuthorEmail() { return authorEmail; } /** * The author's name. * * @return */ public String getAuthorName() { return authorName; } /** * The text of the note. This value is not escaped in anyway and should * never be used as html on the client. * * @return unsafe text content */ public String getContent() { return content; } /** * Gets the height of the note. * * @return */ public int getHeight() { return height; } /** * Gets the object's primary key. * * @return */ public Key getKey() { return key; } /** * Gets the date of the last time this object was persisted. * * @return */ public Date getLastUpdatedAt() { return lastUpdatedAt; } /** * Gets the width of the note. * * @return */ public int getWidth() { return width; } /** * Gets the x position of the note. * * @return */ public int getX() { return x; } /** * Gets the y position of the note. * * @return */ public int getY() { return y; } /** * Indicates whether the given author is the owner of this note. * * @param author * the author * @return <code>true</code> if <code>author</code> is the owner of the * note, <code>false</code> otherwise. */ public boolean isOwnedBy(Author author) { return author.getEmail().equals(authorEmail); } /** * Sets the content. * * @param content */ public void setContent(String content) { this.content = content; } /** * Sets the height of the note. * * @param height */ public void setHeight(int height) { this.height = height; } /** * Sets the width of the note. * * @param width */ public void setWidth(int width) { this.width = width; } /** * Sets the x position of the note. * * @param x */ public void setX(int x) { this.x = x; } /** * Sets the y position of the note. * * @param y */ public void setY(int y) { this.y = y; } } /** * A JDO object representing a surface. */ @PersistenceCapable(identityType = IdentityType.APPLICATION) public static class Surface { /** * An auto-generated primary key. */ @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Key key; /** * The title of the surface. */ @Persistent private String title; /** * The date of the last time this surface was persisted. */ @Persistent private Date lastUpdatedAt; /** * The name of each author that has access to this surface. */ @Persistent(defaultFetchGroup = "true") private List<String> authorNames = new ArrayList<String>(); /** * The notes that are stuck to this surface. */ @Element(dependent = "true") private List<Note> notes = new ArrayList<Note>(); /** * Create a new surface. * * @param title */ public Surface(String title) { this.title = title; } /** * Add the name to the author names. Calls to this method are generally * paired with a call to {@link Author#addSurface(Surface)}. * * @param name */ public void addAuthorName(String name) { final List<String> names = new ArrayList<String>(authorNames); names.add(name); setAuthorNames(names); } /** * Gets the collection of author names. * * @return */ public List<String> getAuthorNames() { return authorNames; } /** * Gets the primary key for this object. * * @return */ public Key getKey() { return key; } /** * Gets the date of the last time this object was persisted. * * @return */ public Date getLastUpdatedAt() { return lastUpdatedAt; } /** * Gets all the notes that are stuck to this surface. * * @return */ public List<Note> getNotes() { return notes; } /** * Get the surface's title. * * @return */ public String getTitle() { return title; } /** * Sets the surface's title. * * @param title */ public void setTitle(String title) { this.title = title; } /** * Reassigns the collection of author names. This is required to ensure that * the ORM persists element collections. * * @param names */ private void setAuthorNames(List<String> names) { authorNames = names; } } private final PersistenceManagerFactory factory; /** * Create a new Store based on a particular config. * * @param config */ public Store(String config) { this.factory = JDOHelper.getPersistenceManagerFactory(config); } /** * Starts a data store session and returns an Api object to use. * * @return */ public Api getApi() { return new Api(); } }