ch.agent.crnickl.mongodb.ReadMethodsForChroniclesAndSeries Maven / Gradle / Ivy
Show all versions of crnickl-mongodb Show documentation
/*
* Copyright 2012-2013 Hauser Olsson GmbH
*
* 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 ch.agent.crnickl.mongodb;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.bson.types.ObjectId;
import ch.agent.crnickl.T2DBException;
import ch.agent.crnickl.T2DBMsg;
import ch.agent.crnickl.T2DBMsg.E;
import ch.agent.crnickl.api.Attribute;
import ch.agent.crnickl.api.Chronicle;
import ch.agent.crnickl.api.DBObjectType;
import ch.agent.crnickl.api.Database;
import ch.agent.crnickl.api.Property;
import ch.agent.crnickl.api.Series;
import ch.agent.crnickl.api.Surrogate;
import ch.agent.crnickl.impl.ChronicleImpl;
import ch.agent.crnickl.impl.Permission;
import ch.agent.crnickl.impl.SeriesImpl;
import ch.agent.crnickl.mongodb.T2DBMMsg.J;
import com.mongodb.BasicDBObject;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
/**
* A stateless object with methods providing read access to chronicles and
* series.
*
* @author Jean-Paul Vetterli
*/
public class ReadMethodsForChroniclesAndSeries extends MongoDatabaseMethods {
public ReadMethodsForChroniclesAndSeries() {
}
/**
* Find a chronicle corresponding to a surrogate. An
* exception is thrown if there is no such chronicle.
*
* @param s a surrogate
* @return a chronicle, never null
* @throws T2DBException
*/
public Chronicle getChronicle(Surrogate s) throws T2DBException {
DBObject obj = getObject(s, false);
Throwable cause = null;
if (obj != null)
try {
Chronicle chronicle = unpack(s.getDatabase(), (BasicDBObject) obj);
check(Permission.READ, chronicle);
return chronicle;
} catch (Exception e) {
cause = e;
}
throw T2DBMsg.exception(cause, E.E40104, s.toString());
}
/**
* Find a chronicle with a given parent and name.
*
* @param parent a chronicle
* @param name a string
* @return a chronicle or null
* @throws T2DBException
*/
public Chronicle getChronicleOrNull(Chronicle parent, String name) throws T2DBException {
try {
Database database = parent.getDatabase();
DBObject obj = getMongoDB(database).getChronicles().findOne(
mongoObject(MongoDatabase.FLD_CHRON_PARENT, getIdOrZero(parent),
MongoDatabase.FLD_CHRON_NAME, name));
if (obj != null) {
Chronicle chronicle = unpack(database, (BasicDBObject) obj);
check(Permission.READ, chronicle);
return chronicle;
} else
return null;
} catch (Exception e) {
throw T2DBMsg.exception(e, E.E40123, name, parent.getName(true));
}
}
public Collection getChroniclesByParent(Chronicle parent) throws T2DBException {
Collection result = new ArrayList();
if (check(Permission.DISCOVER, parent, false)) {
try {
Database db = parent.getDatabase();
DBCursor cursor = getMongoDB(db).getChronicles().find(
mongoObject(MongoDatabase.FLD_CHRON_PARENT, getIdOrZero(parent)));
try {
while (cursor.hasNext()) {
Chronicle chronicle = unpack(db, (BasicDBObject) cursor.next());
check(Permission.READ, chronicle);
result.add(chronicle);
}
} finally {
cursor.close();
}
} catch (Exception e) {
throw T2DBMsg.exception(e, E.E40122, parent.getName(true));
}
}
return result;
}
/**
* The method completes the attribute with the value found for one of the
* entities in the list and returns true. If no value was found, the method
* return false. If more than value was found, the one selected corresponds
* to the first entity encountered in the list.
*
* @param chronicles a list of chronicles
* @param attribute an attribute
* @return true if any value found
* @throws T2DBException
*/
public boolean getAttributeValue(List chronicles, Attribute> attribute) throws T2DBException {
boolean found = false;
Surrogate s = attribute.getProperty().getSurrogate();
Database db = s.getDatabase();
try {
ObjectId[] chrOids = new ObjectId[chronicles.size()];
int offset = 0;
for(Chronicle chronicle : chronicles) {
chrOids[offset++] = getId(chronicle);
}
DBCursor cursor = getMongoDB(db).getAttributes().find(
mongoObject(MongoDatabase.FLD_ATTR_PROP,
getId(s), MongoDatabase.FLD_ATTR_CHRON,
mongoObject(Operator.IN.op(), chrOids)));
offset = Integer.MAX_VALUE;
BasicDBObject objAtOffset = null;
while (cursor.hasNext()) {
BasicDBObject obj = (BasicDBObject) cursor.next();
ObjectId chrOid = obj.getObjectId(MongoDatabase.FLD_ATTR_CHRON);
int offset1 = findOffset(chrOid, chronicles);
if (offset1 < offset) {
offset = offset1;
objAtOffset = obj;
}
if (offset == 0)
break;
}
if (objAtOffset != null) {
ObjectId chrOid = objAtOffset.getObjectId(MongoDatabase.FLD_ATTR_CHRON);
Surrogate chronicle = makeSurrogate(db, DBObjectType.CHRONICLE, chrOid);
check(Permission.READ, chronicle);
attribute.scan(objAtOffset.getString(MongoDatabase.FLD_ATTR_VALUE));
String description = objAtOffset.getString(MongoDatabase.FLD_ATTR_DESC);
if (description.length() > 0)
attribute.setDescription(description);
found = true;
}
} catch (Exception e) {
throw T2DBMsg.exception(e, E.E40120, attribute.getProperty().getName());
}
return found;
}
/**
* Return a list of chronicles with a given value for a given property.
*
* @param property a property
* @param value a value
* @param maxSize the maximum size of the result or 0 for no limit
* @return a list of chronicles, possibly empty, never null
* @throws T2DBException
*/
public List getChroniclesByAttributeValue(Property property, T value, int maxSize) throws T2DBException {
List result = new ArrayList();
Surrogate s = property.getSurrogate();
Database db = s.getDatabase();
try {
DBCursor cursor = getMongoDB(db).getAttributes().find(
mongoObject(MongoDatabase.FLD_ATTR_PROP,
getId(s), MongoDatabase.FLD_ATTR_VALUE,
property.getValueType().toString(value)));
while (cursor.hasNext()) {
BasicDBObject obj = (BasicDBObject) cursor.next();
ObjectId chrOid = obj.getObjectId(MongoDatabase.FLD_ATTR_CHRON);
Surrogate chronicle = makeSurrogate(db, DBObjectType.CHRONICLE, chrOid);
try {
check(Permission.READ, chronicle);
result.add(new ChronicleImpl(chronicle));
} catch (T2DBException e) {
// ignore "unreadable" chronicles
}
}
} catch (Exception e) {
throw T2DBMsg.exception(e, E.E40119, property.getName(), value);
}
return result;
}
/**
* Find a series corresponding to a surrogate.
*
* @param s a surrogate
* @return a series or null
* @throws T2DBException
*/
public Series getSeries(Surrogate s) throws T2DBException {
BasicDBObject obj = (BasicDBObject) getObject(s, false);
Throwable cause = null;
if (obj != null)
try {
ObjectId chrOid = obj.getObjectId(MongoDatabase.FLD_SER_CHRON);
int serNum = obj.getInt(MongoDatabase.FLD_SER_NUM);
Chronicle chronicle = new ChronicleImpl(
makeSurrogate(s.getDatabase(), DBObjectType.CHRONICLE, chrOid));
check(Permission.READ, chronicle);
Series series = new SeriesImpl(chronicle, null, serNum, s);
return series;
} catch (Exception e) {
cause = e;
}
throw T2DBMsg.exception(cause, E.E40104, s.toString());
}
/**
* Return array of series in the positions corresponding to the
* requested numbers. These numbers are series numbers within the schema,
* not the series keys in the database. When a requested number in the array is
* non-positive, the corresponding series will be null in the result. A
* series can also be null when the requested number is positive but the
* series has no data and has not been set up in the database (and therefore
* has no series database key).
*
* Note. There is no exception if the chronicle does not exist. This behavior makes
* it easier to implement the wishful transaction protocol (i.e. testing again if it
* was okay to delete a chronicle after it was deleted).
*
* @param chronicle a chronicle
* @param names an array of simple names to plug into the series
* @param numbers an array of numbers
* @return an array of series
* @throws T2DBException
*/
public Series[] getSeries(Chronicle chronicle, String[] names, int[] numbers) throws T2DBException {
if (names.length != numbers.length)
throw new IllegalArgumentException("lengths of names[] and numbers[] differ");
@SuppressWarnings("unchecked")
Series[] result = new SeriesImpl[numbers.length];
if (result.length > 0) {
Map map = new HashMap();
for (int i = 0; i < numbers.length; i++) {
if (numbers[i] > 0) {
if (map.put(numbers[i], i) != null)
throw new IllegalArgumentException("duplicate number: " + numbers[i]);
}
}
Surrogate s = chronicle.getSurrogate();
Database db = s.getDatabase();
try {
check(Permission.READ, chronicle);
} catch (Exception e) {
throw T2DBMsg.exception(e, E.E40104, s.toString());
}
try {
DBCursor cursor = getMongoDB(db).getSeries().find(
mongoObject(MongoDatabase.FLD_SER_CHRON,
getId(chronicle), MongoDatabase.FLD_SER_NUM,
mongoObject(Operator.IN.op(), numbers)),
mongoObject(MongoDatabase.FLD_SER_NUM, 1));
while (cursor.hasNext()) {
BasicDBObject obj = (BasicDBObject) cursor.next();
ObjectId serOid = obj.getObjectId(MongoDatabase.FLD_ID);
int serNumber = obj.getInt(MongoDatabase.FLD_SER_NUM);
int i = map.get(serNumber);
result[i] = new SeriesImpl(chronicle,
names[i], numbers[i],
makeSurrogate(db, DBObjectType.SERIES, serOid));
}
} catch (Exception e) {
throw T2DBMsg.exception(e, E.E40121, chronicle.getName(true));
}
}
return result;
}
private Chronicle unpack(Database db, BasicDBObject obj) throws T2DBException {
try {
ObjectId id = obj.getObjectId(MongoDatabase.FLD_ID);
Surrogate s = makeSurrogate(db, DBObjectType.CHRONICLE, id);
ChronicleImpl.RawData data = new ChronicleImpl.RawData();
data.setSurrogate(s);
data.setName(obj.getString(MongoDatabase.FLD_CHRON_NAME));
data.setDescription(obj.getString(MongoDatabase.FLD_CHRON_DESC));
ObjectId parentId = obj.getObjectId(MongoDatabase.FLD_CHRON_PARENT);
data.setCollection(parentId == null ? db.getTopChronicle() :
new ChronicleImpl(makeSurrogate(db, DBObjectType.CHRONICLE, parentId)));
ObjectId schemaId = obj.getObjectId(MongoDatabase.FLD_CHRON_SCHEMA);
data.setSchema(schemaId == null ? null : makeSurrogate(db, DBObjectType.SCHEMA, schemaId));
Chronicle chronicle = new ChronicleImpl(data);
return chronicle;
} catch (ClassCastException e) {
throw T2DBMMsg.exception(e, J.J81010, obj.toString());
}
}
private int findOffset(ObjectId id, List chronicles) {
int found = -1;
int offset = 0;
for(Chronicle chronicle : chronicles) {
if (getId(chronicle).equals(id)) {
found = offset;
break;
}
offset++;
}
if (found < 0)
throw new RuntimeException("chronicle not found: " + id);
return found;
}
}