/*
* Copyright 2012 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 org.vitaliyl.monitoring.server;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
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.EntityNotFoundException;
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.PreparedQuery;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.FilterOperator;
import com.google.appengine.api.datastore.Transaction;
/**
* Simple implementation of a data store using standard Java collections.
* <p>
* This class is neither persistent (it will lost the data when the app is
* restarted) nor thread safe.
*/
public final class Datastore {
static final int MULTICAST_SIZE = 1000;
public static final String DEVICE_TYPE = "Device";
private static final String DEVICE_REG_ID_PROPERTY = "regId";
public static final String MULTICAST_TYPE = "Multicast";
private static final String MULTICAST_REG_IDS_PROPERTY = "regIds";
public static final String MESSAGE_TYPE = "MessageDescriptor";
public static final String SERVER_TYPE = "ServerDescriptor";
public static final String SERVER_SERVER_URL = "MessageDescriptorServerUrl";
public static final String SERVER_PROBLEM_TIME = "MessageDescriptorServerWhen";
public static final String SERVER_IS_DOWN = "MessageDescriptorIsDown";
public static final String SERVER_CONNECTIONS = "MessageDescriptorConnections";
public static final String SERVER_MAX_CONNECTIONS = "MessageDescriptorMaxConnections";
public static final FetchOptions DEFAULT_FETCH_OPTIONS = FetchOptions.Builder.withPrefetchSize(MULTICAST_SIZE)
.chunkSize(MULTICAST_SIZE);
public static final Logger logger = Logger.getLogger(Datastore.class.getName());
public static final DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
private Datastore() {
throw new UnsupportedOperationException();
}
/**
* Registers a device.
*
* @param regId
* device's registration id.
*/
public static void register(String regId) {
logger.info("Registering " + regId);
Transaction txn = datastore.beginTransaction();
try {
Entity entity = findDeviceByRegId(regId);
if (entity != null) {
logger.fine(regId + " is already registered; ignoring.");
return;
}
entity = new Entity(DEVICE_TYPE);
entity.setProperty(DEVICE_REG_ID_PROPERTY, regId);
datastore.put(entity);
txn.commit();
} finally {
if (txn.isActive()) {
txn.rollback();
}
}
}
/**
* Unregisters a device.
*
* @param regId
* device's registration id.
*/
public static void unregister(String regId) {
logger.info("Unregistering " + regId);
Transaction txn = datastore.beginTransaction();
try {
Entity entity = findDeviceByRegId(regId);
if (entity == null) {
logger.warning("Device " + regId + " already unregistered");
} else {
Key key = entity.getKey();
datastore.delete(key);
}
txn.commit();
} finally {
if (txn.isActive()) {
txn.rollback();
}
}
}
/**
* Updates the registration id of a device.
*/
public static void updateRegistration(String oldId, String newId) {
logger.info("Updating " + oldId + " to " + newId);
Transaction txn = datastore.beginTransaction();
try {
Entity entity = findDeviceByRegId(oldId);
if (entity == null) {
logger.warning("No device for registration id " + oldId);
return;
}
entity.setProperty(DEVICE_REG_ID_PROPERTY, newId);
datastore.put(entity);
txn.commit();
} finally {
if (txn.isActive()) {
txn.rollback();
}
}
}
/**
* Gets all registered devices.
*/
public static List<String> getDevices() {
List<String> devices;
Transaction txn = datastore.beginTransaction();
try {
Query query = new Query(DEVICE_TYPE);
Iterable<Entity> entities = datastore.prepare(query)
.asIterable(DEFAULT_FETCH_OPTIONS);
devices = new ArrayList<String>();
for (Entity entity : entities) {
String device = (String) entity.getProperty(DEVICE_REG_ID_PROPERTY);
devices.add(device);
}
txn.commit();
} finally {
if (txn.isActive()) {
txn.rollback();
}
}
return devices;
}
/**
* Gets the number of total devices.
*/
public static int getTotalDevices() {
Transaction txn = datastore.beginTransaction();
try {
Query query = new Query(DEVICE_TYPE).setKeysOnly();
List<Entity> allKeys = datastore.prepare(query)
.asList(DEFAULT_FETCH_OPTIONS);
int total = allKeys.size();
logger.fine("Total number of devices: " + total);
txn.commit();
return total;
} finally {
if (txn.isActive()) {
txn.rollback();
}
}
}
private static Entity findDeviceByRegId(String regId) {
Query query = new Query(DEVICE_TYPE).addFilter(DEVICE_REG_ID_PROPERTY, FilterOperator.EQUAL, regId);
PreparedQuery preparedQuery = datastore.prepare(query);
List<Entity> entities = preparedQuery.asList(DEFAULT_FETCH_OPTIONS);
Entity entity = null;
if (!entities.isEmpty()) {
entity = entities.get(0);
}
int size = entities.size();
if (size > 0) {
logger.severe("Found " + size + " entities for regId " + regId + ": " + entities);
}
return entity;
}
/**
* Creates a persistent record with the devices to be notified using a
* multicast message.
*
* @param devices
* registration ids of the devices.
* @return encoded key for the persistent record.
*/
public static String createMulticast(List<String> devices) {
logger.info("Storing multicast for " + devices.size() + " devices");
String encodedKey;
Transaction txn = datastore.beginTransaction();
try {
Entity entity = new Entity(MULTICAST_TYPE);
entity.setProperty(MULTICAST_REG_IDS_PROPERTY, devices);
datastore.put(entity);
Key key = entity.getKey();
encodedKey = KeyFactory.keyToString(key);
logger.fine("multicast key: " + encodedKey);
txn.commit();
} finally {
if (txn.isActive()) {
txn.rollback();
}
}
return encodedKey;
}
/**
* Gets a persistent record with the devices to be notified using a
* multicast message.
*
* @param encodedKey
* encoded key for the persistent record.
*/
public static List<String> getMulticast(String encodedKey) {
Key key = KeyFactory.stringToKey(encodedKey);
Entity entity;
Transaction txn = datastore.beginTransaction();
try {
entity = datastore.get(key);
@SuppressWarnings("unchecked")
List<String> devices = (List<String>) entity.getProperty(MULTICAST_REG_IDS_PROPERTY);
txn.commit();
return devices;
} catch (EntityNotFoundException e) {
logger.severe("No entity for key " + key);
return Collections.emptyList();
} finally {
if (txn.isActive()) {
txn.rollback();
}
}
}
/**
* Updates a persistent record with the devices to be notified using a
* multicast message.
*
* @param encodedKey
* encoded key for the persistent record.
* @param devices
* new list of registration ids of the devices.
*/
public static void updateMulticast(String encodedKey, List<String> devices) {
Key key = KeyFactory.stringToKey(encodedKey);
Entity entity;
Transaction txn = datastore.beginTransaction();
try {
try {
entity = datastore.get(key);
} catch (EntityNotFoundException e) {
logger.severe("No entity for key " + key);
return;
}
entity.setProperty(MULTICAST_REG_IDS_PROPERTY, devices);
datastore.put(entity);
txn.commit();
} finally {
if (txn.isActive()) {
txn.rollback();
}
}
}
/**
* Deletes a persistent record with the devices to be notified using a
* multicast message.
*
* @param encodedKey
* encoded key for the persistent record.
*/
public static void deleteEntity(String encodedKey) {
Transaction txn = datastore.beginTransaction();
try {
Key key = KeyFactory.stringToKey(encodedKey);
datastore.delete(key);
txn.commit();
} finally {
if (txn.isActive()) {
txn.rollback();
}
}
}
public static void deleteAllEntities(String type) {
Query query = new Query(type);
PreparedQuery preparedQuery = datastore.prepare(query);
List<Entity> entities = preparedQuery.asList(DEFAULT_FETCH_OPTIONS);
for (int i = 0; i < entities.size(); i++) {
datastore.delete(entities.get(i)
.getKey());
}
}
}