/**
* Copyright 2015 ArcBees 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.arcbees.gaestudio.server.service.visualizer;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;
import javax.inject.Inject;
import com.arcbees.gaestudio.server.util.AppEngineHelper;
import com.arcbees.gaestudio.server.util.DatastoreCountProvider;
import com.arcbees.gaestudio.server.util.DatastoreHelper;
import com.arcbees.gaestudio.server.util.DefaultValueGenerator;
import com.arcbees.gaestudio.shared.DeleteEntities;
import com.google.appengine.api.NamespaceManager;
import com.google.appengine.api.datastore.AsyncDatastoreService;
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.EntityTranslator;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Query;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import com.google.storage.onestore.v3.OnestoreEntity.EntityProto;
public class EntitiesServiceImpl implements EntitiesService {
private final DatastoreHelper datastoreHelper;
private final DefaultValueGenerator defaultValueGenerator;
private final DatastoreCountProvider countProvider;
@Inject
EntitiesServiceImpl(
DatastoreHelper datastoreHelper,
DefaultValueGenerator defaultValueGenerator,
DatastoreCountProvider countProvider) {
this.datastoreHelper = datastoreHelper;
this.defaultValueGenerator = defaultValueGenerator;
this.countProvider = countProvider;
}
@Override
public Iterable<Entity> getEntities(String kind, String namespace, Integer offset, Integer limit) {
AppEngineHelper.disableApiHooks();
FetchOptions fetchOptions = FetchOptions.Builder.withDefaults();
if (offset != null) {
fetchOptions.offset(offset);
}
if (limit != null) {
fetchOptions.limit(limit);
}
Query query = new Query(kind);
return datastoreHelper.queryOnNamespace(namespace, query, fetchOptions);
}
@Override
public Collection<Entity> getEntities(List<Key> keys) {
AppEngineHelper.disableApiHooks();
DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
return datastore.get(keys).values();
}
@Override
public Entity createEmptyEntity(String kind) {
AppEngineHelper.disableApiHooks();
DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
// TODO: Entities can have multiple model versions. We should either select the latest entity or specify an ID
// to fetch the template (ie. the selected entity) so we get a
Query query = new Query(kind);
FetchOptions fetchOptions = FetchOptions.Builder.withOffset(0).limit(1);
List<Entity> entities = datastore.prepare(query).asList(fetchOptions);
Entity emptyEntity = null;
if (!entities.isEmpty()) {
Entity template = entities.get(0);
emptyEntity = createEmptyEntityFromTemplate(template);
}
return emptyEntity;
}
@Override
public void deleteEntities(String kind, String namespace, DeleteEntities deleteType, String encodedKeys) {
AppEngineHelper.disableApiHooks();
switch (deleteType) {
case KIND:
deleteByKind(kind);
break;
case NAMESPACE:
deleteByNamespace(namespace);
break;
case KIND_NAMESPACE:
deleteByKindAndNamespace(kind, namespace);
break;
case ALL:
deleteAll();
break;
case SET:
deleteSet(encodedKeys);
break;
}
}
@Override
public long getCount(String kind, String namespace) {
AppEngineHelper.disableApiHooks();
return countProvider.get(kind, namespace);
}
@Override
public List<Key> put(Iterable<Entity> entities) {
AppEngineHelper.disableApiHooks();
DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
return datastore.put(entities);
}
@Override
public Future<List<Key>> putAsync(Iterable<Entity> entities) {
AppEngineHelper.disableApiHooks();
AsyncDatastoreService datastoreService = DatastoreServiceFactory.getAsyncDatastoreService();
return datastoreService.put(entities);
}
private Entity createEmptyEntityFromTemplate(Entity template) {
// Copy the entity from a known prototype, keeping the type metadata
EntityProto templateProto = EntityTranslator.convertToPb(template);
templateProto.getKey().getPath().getElement(0).clearId();
Entity emptyEntity = EntityTranslator.createFromPb(templateProto);
emptyProperties(emptyEntity);
return emptyEntity;
}
private void emptyProperties(Entity entity) {
Map<String, Object> properties = entity.getProperties();
for (Map.Entry<String, Object> property : properties.entrySet()) {
String propertyKey = property.getKey();
Object value = property.getValue();
if (value instanceof Key) {
value = createEmptyKey((Key) value);
} else {
value = createEmptyPropertyObject(value);
}
if (entity.isUnindexedProperty(propertyKey)) {
entity.setUnindexedProperty(propertyKey, value);
} else {
entity.setProperty(propertyKey, value);
}
}
}
private Object createEmptyKey(Key key) {
return KeyFactory.createKey(key.getKind(), " ");
}
private Object createEmptyPropertyObject(Object property) {
return defaultValueGenerator.generate(property);
}
private void deleteSet(String encodedKeys) {
Iterable<String> stringKeys = Splitter.on(",").split(encodedKeys);
List<Key> keys = Lists.newArrayList();
for (String key : stringKeys) {
keys.add(KeyFactory.stringToKey(key));
}
deleteKeys(keys);
}
private void deleteByNamespace(String namespace) {
String defaultNamespace = NamespaceManager.get();
NamespaceManager.set(namespace);
Iterable<Entity> entities = getAllEntitiesInCurrentNamespace();
deleteEntities(entities);
NamespaceManager.set(defaultNamespace);
}
private void deleteByKindAndNamespace(String kind, String namespace) {
String defaultNamespace = NamespaceManager.get();
NamespaceManager.set(namespace);
Iterable<Entity> entities = getAllEntitiesOfKind(kind);
deleteEntities(entities);
NamespaceManager.set(defaultNamespace);
}
private void deleteByKind(String kind) {
Query query = new Query(kind).setKeysOnly();
datastoreHelper.deleteOnAllNamespaces(query);
}
private void deleteAll() {
Iterable<Entity> entities = getAllEntitiesOfAllNamespaces();
deleteEntities(entities);
}
private Iterable<Entity> getAllEntitiesInCurrentNamespace() {
DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
Query query = new Query().setKeysOnly();
datastoreHelper.preFilterGaeKinds(query);
return datastore.prepare(query).asIterable();
}
private Iterable<Entity> getAllEntitiesOfKind(String kind) {
DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
return datastore.prepare(new Query(kind).setKeysOnly()).asIterable();
}
private void deleteEntities(Iterable<Entity> entities) {
DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
for (Entity entity : entities) {
datastore.delete(entity.getKey());
}
}
private void deleteKeys(List<Key> keys) {
DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
datastore.delete(keys);
}
private Iterable<Entity> getAllEntitiesOfAllNamespaces() {
return datastoreHelper.queryOnAllNamespaces(new Query().setKeysOnly());
}
}