/**
* Copyright (c) 2010-2011 Ed Merks and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ed Merks - Initial API and implementation
*/
package org.eclipse.emf.server.ecore.resource;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EAnnotation;
import org.eclipse.emf.ecore.EcoreFactory;
import org.eclipse.emf.ecore.plugin.EcorePlugin;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.URIConverter;
import org.eclipse.emf.ecore.resource.impl.BinaryResourceImpl;
import org.eclipse.emf.ecore.resource.impl.ResourceFactoryImpl;
import com.google.appengine.api.datastore.Blob;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.PreparedQuery;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Transaction;
final public class DatastoreUtil
{
public static final String OPTION_SESSION = "SESSION";
static
{
EcorePlugin.DEFAULT_URI_HANDLERS.add(new DatastoreURIHandlerImpl());
EcorePlugin.DEFAULT_URI_HANDLERS.add(new URIHandlerImpl());
Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put
(Resource.Factory.Registry.DEFAULT_EXTENSION,
new ResourceFactoryImpl()
{
@Override
public Resource createResource(URI uri)
{
return new BinaryResourceImpl(uri);
}
});
}
private DatastoreUtil()
{
// Do nothing.
}
public static class ResourceFilter
{
public boolean isIncluded(Entity resourceEntity)
{
String uri = (String)resourceEntity.getProperty("uri");
return isIncluded(uri);
}
public boolean isIncluded(String resourceURI)
{
return true;
}
}
public static List<Resource> getResources(ResourceSet resourceSet, ResourceFilter resourceFilter) throws IOException
{
return getResources(resourceSet, resourceFilter, null);
}
public static List<Resource> getResources(ResourceSet resourceSet, ResourceFilter resourceFilter, String sessionID) throws IOException
{
List<Resource> result = new ArrayList<Resource>();
DatastoreService datastoreService = DatastoreServiceFactory.getDatastoreService();
Entity session = getSession(datastoreService, sessionID);
Key sessionKey = session == null ? null : session.getKey();
Query query = new Query("org.eclipse.emf.ecore.resource", sessionKey);
PreparedQuery preparedQuery = datastoreService.prepare(query);
for (Entity entity : preparedQuery.asIterable())
{
if (resourceFilter.isIncluded(entity) && (sessionKey != null || entity.getParent() == null))
{
URI uri = URI.createURI((String)entity.getProperty("uri"));
Resource resource = resourceSet.getResource(uri, false);
if (resource == null)
{
resource = resourceSet.createResource(uri);
}
if (!resource.isLoaded())
{
resource.load(new ByteArrayInputStream(getBytes(entity, datastoreService)), resourceSet.getLoadOptions());
resource.setTimeStamp((Long)entity.getProperty("timestamp"));
}
result.add(resource);
}
}
return result;
}
public static List<URI> getResources()
{
return getResources(null);
}
public static List<URI> getResources(String sessionID)
{
List<URI> result = new ArrayList<URI>();
DatastoreService datastoreService = DatastoreServiceFactory.getDatastoreService();
Entity session = getSession(datastoreService, sessionID);
Key sessionKey = session == null ? null : session.getKey();
Query query = new Query("org.eclipse.emf.ecore.resource", sessionKey);
PreparedQuery preparedQuery = datastoreService.prepare(query);
for (Entity entity : preparedQuery.asIterable())
{
if (sessionKey != null || entity.getKey() == null)
{
URI uri = URI.createURI((String)entity.getProperty("uri"));
result.add(uri);
}
}
return result;
}
public static Entity getEntity(Key sessionKey, PreparedQuery query)
{
if (sessionKey == null)
{
for (Entity entity : query.asIterable())
{
if (entity.getParent() == null)
{
return entity;
}
}
return null;
}
else
{
return query.asSingleEntity();
}
}
public static Map<?, ?> fetch(String uri, Map<?, ?> options)
{
@SuppressWarnings("unchecked")
Map<Object, Object> result = (Map<Object, Object>)options.get(URIConverter.OPTION_RESPONSE);
DatastoreService datastoreService = DatastoreServiceFactory.getDatastoreService();
Entity session = getSession(datastoreService, (String)options.get(OPTION_SESSION));
Key sessionKey = session == null ? null : session.getKey();
Query query = new Query("org.eclipse.emf.ecore.resource", sessionKey);
if (uri.equals("datastore:/"))
{
Resource resource = new BinaryResourceImpl(URI.createURI(uri));
EAnnotation eAnnotation = EcoreFactory.eINSTANCE.createEAnnotation();
resource.getContents().add(eAnnotation);
PreparedQuery preparedQuery = datastoreService.prepare(query);
for (Entity entity : preparedQuery.asIterable())
{
if (sessionKey != null || entity.getParent() == null)
{
eAnnotation.getDetails().put(entity.getProperty("uri").toString(), entity.getProperty("timestamp").toString());
}
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
try
{
resource.save(out, null);
}
catch (IOException exception)
{
// TODO
}
result.put(URIConverter.RESPONSE_RESULT, out.toByteArray());
}
else
{
// Transaction transaction = datastoreService.beginTransaction();
// Find the existing entity, if any.
//
query.addFilter("uri", Query.FilterOperator.EQUAL, uri);
PreparedQuery preparedQuery = datastoreService.prepare(query);
Entity entity = getEntity(sessionKey, preparedQuery);
if (entity != null)
{
result.put(URIConverter.RESPONSE_TIME_STAMP_PROPERTY, entity.getProperty("timestamp"));
byte[] bytes = getBytes(entity, datastoreService);
result.put(URIConverter.RESPONSE_RESULT, bytes);
}
else
{
result.put(URIConverter.RESPONSE_RESULT, null);
}
}
// transaction.rollback();
return options;
}
public static byte[] getBytes(Entity entity, DatastoreService datastoreService)
{
if (entity.hasProperty("content"))
{
Blob blob = (Blob)entity.getProperty("content");
return blob.getBytes();
}
else
{
Query contentBlobsQuery = new Query("ChildBlob", entity.getKey());
PreparedQuery preparedContentBlobsQuery = datastoreService.prepare(contentBlobsQuery);
Map<Long, byte[]> contents = new TreeMap<Long, byte[]>();
int length = 0;
for (Entity contentBlobEntity : preparedContentBlobsQuery.asIterable())
{
byte[] childBytes = ((Blob)contentBlobEntity.getProperty("value")).getBytes();
contents.put((Long)contentBlobEntity.getProperty("index"), childBytes);
length += childBytes.length;
}
byte[] bytes = new byte[length];
int offset = 0;
for (byte[] childBytes : contents.values())
{
System.arraycopy(childBytes, 0, bytes, offset, childBytes.length);
offset += childBytes.length;
}
return bytes;
}
}
protected static final int MAX_BLOB_SIZE = 1000000;
public static Entity getSession(DatastoreService datastoreService, String session)
{
Entity entity = null;
if (session != null)
{
Query query = new Query("org.eclipse.emf.ecore.resource.session");
query.addFilter("id", Query.FilterOperator.EQUAL, session);
PreparedQuery preparedQuery = datastoreService.prepare(query);
entity = preparedQuery.asSingleEntity();
if (entity == null)
{
entity = new Entity("org.eclipse.emf.ecore.resource.session");
entity.setProperty("id", session);
datastoreService.put(entity);
}
}
return entity;
}
public static Map<?, ?> store(String uri, byte[] bytes, Map<?, ?> options)
{
@SuppressWarnings("unchecked")
Map<Object, Object> result = (Map<Object, Object>)options.get(URIConverter.OPTION_RESPONSE);
DatastoreService datastoreService = DatastoreServiceFactory.getDatastoreService();
Transaction transaction = datastoreService.beginTransaction();
Entity session = getSession(datastoreService, (String)options.get(OPTION_SESSION));
Key sessionKey = session == null ? null : session.getKey();
Query query = new Query("org.eclipse.emf.ecore.resource", sessionKey);
query.addFilter("uri", Query.FilterOperator.EQUAL, uri);
PreparedQuery preparedQuery = datastoreService.prepare(query);
Entity entity = getEntity(sessionKey, preparedQuery);
Long expectedTimestamp = (Long)options.get(URIConverter.OPTION_UPDATE_ONLY_IF_TIME_STAMP_MATCHES);
if (entity == null)
{
// Return early without a timestamp in the response to indicate failure.
//
if (expectedTimestamp != null)
{
transaction.rollback();
result.put(URIConverter.RESPONSE_RESULT, new IOException("The timestamp for '" + uri + "' doesn't match the expected time stamp"));
return options;
}
// Create the entity if it doesn't exist.
//
entity = session == null? new Entity("org.eclipse.emf.ecore.resource") : new Entity("org.eclipse.emf.ecore.resource", session.getKey());
entity.setProperty("uri", uri);
}
// If it doesn't have a content property, it must have content blob children.
//
else
{
// Return early without a timestamp in the response to indicate failure.
//
if (expectedTimestamp != null && expectedTimestamp.intValue() != ((Long)entity.getProperty("timestamp")).intValue())
{
transaction.rollback();
result.put(URIConverter.RESPONSE_RESULT, new IOException("The timestamp for '" + uri + "' doesn't match the expected time stamp"));
return options;
}
if (entity.hasProperty("content"))
{
entity.removeProperty("content");
}
else
{
// Delete all those children.
//
Query contentBlobsQuery = new Query("ChildBlob", entity.getKey());
PreparedQuery preparedContentBlobsQuery = datastoreService.prepare(transaction, contentBlobsQuery);
for (Entity contentBlobEntity : preparedContentBlobsQuery.asIterable())
{
datastoreService.delete(transaction, contentBlobEntity.getKey());
}
}
}
// Keep a timestamp property
//
long timestamp = System.currentTimeMillis();
entity.setUnindexedProperty("timestamp", timestamp);
// Determine if the bytes need to be factored into child blob entities.
//
if (bytes.length > MAX_BLOB_SIZE)
{
Key entityKey = datastoreService.put(transaction, entity);
for (int i = 0, offset = 0; offset < bytes.length; offset += MAX_BLOB_SIZE, ++i)
{
int length = Math.min(bytes.length - offset, MAX_BLOB_SIZE);
byte [] childBytes = new byte[length];
System.arraycopy(bytes, offset, childBytes, 0, length);
Entity childBlobEntity = new Entity("ChildBlob", entityKey);
childBlobEntity.setProperty("index", i);
childBlobEntity.setUnindexedProperty("value", new Blob(childBytes));
datastoreService.put(transaction, childBlobEntity);
}
}
else
{
entity.setUnindexedProperty("content", new Blob(bytes));
datastoreService.put(transaction, entity);
}
transaction.commit();
result.put(URIConverter.RESPONSE_TIME_STAMP_PROPERTY, timestamp);
result.put(URIConverter.RESPONSE_RESULT, null);
return options;
}
public static Map<?, ?> delete(String uri, Map<?, ?> options)
{
@SuppressWarnings("unchecked")
Map<Object, Object> result = (Map<Object, Object>)options.get(URIConverter.OPTION_RESPONSE);
DatastoreService datastoreService = DatastoreServiceFactory.getDatastoreService();
Entity session = getSession(datastoreService, (String)options.get(OPTION_SESSION));
Key sessionKey = session == null ? null : session.getKey();
Query query = new Query("org.eclipse.emf.ecore.resource", sessionKey);
query.addFilter("uri", Query.FilterOperator.EQUAL, uri);
PreparedQuery preparedQuery = datastoreService.prepare(query);
Entity entity = getEntity(sessionKey, preparedQuery);
if (entity != null)
{
Long expectedTimestamp = (Long)options.get(URIConverter.OPTION_UPDATE_ONLY_IF_TIME_STAMP_MATCHES);
if (expectedTimestamp != null && expectedTimestamp.longValue() != ((Long)entity.getProperty("timestamp")).longValue())
{
result.put(URIConverter.RESPONSE_RESULT, new IOException("The timestamp for '" + uri + "' doesn't match the expected time stamp"));
return options;
}
Transaction transaction = datastoreService.beginTransaction();
if (!entity.hasProperty("content"))
{
// Delete all those children.
//
Query contentBlobsQuery = new Query("ChildBlob", entity.getKey());
PreparedQuery preparedContentBlobsQuery = datastoreService.prepare(transaction, contentBlobsQuery);
for (Entity contentBlobEntity : preparedContentBlobsQuery.asIterable())
{
datastoreService.delete(transaction, contentBlobEntity.getKey());
}
}
datastoreService.delete(transaction, entity.getKey());
transaction.commit();
result.put(URIConverter.RESPONSE_RESULT, null);
}
else
{
result.put(URIConverter.RESPONSE_RESULT, new IOException("No stream found for '" + uri + "'"));
}
return options;
}
public static boolean exists(String uri, Map<?, ?> options)
{
DatastoreService datastoreService = DatastoreServiceFactory.getDatastoreService();
Entity session = getSession(datastoreService, (String)options.get(OPTION_SESSION));
Key sessionKey = session == null ? null : session.getKey();
Query query = new Query("org.eclipse.emf.ecore.resource", sessionKey);
query.addFilter("uri", Query.FilterOperator.EQUAL, uri);
PreparedQuery preparedQuery = datastoreService.prepare(query);
Entity entity = getEntity(sessionKey, preparedQuery);
return entity != null;
}
}