All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ch.agent.crnickl.mongodb.MongoDatabase Maven / Gradle / Ivy

There is a newer version: 2.0.1
Show newest version
/*
 *   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.Collection;
import java.util.List;
import java.util.Random;

import ch.agent.crnickl.T2DBException;
import ch.agent.crnickl.T2DBMsg;
import ch.agent.crnickl.T2DBMsg.D;
import ch.agent.crnickl.T2DBMsg.E;
import ch.agent.crnickl.api.Attribute;
import ch.agent.crnickl.api.AttributeDefinition;
import ch.agent.crnickl.api.Chronicle;
import ch.agent.crnickl.api.DBObjectId;
import ch.agent.crnickl.api.DBObjectType;
import ch.agent.crnickl.api.DatabaseConfiguration;
import ch.agent.crnickl.api.Property;
import ch.agent.crnickl.api.Schema;
import ch.agent.crnickl.api.Series;
import ch.agent.crnickl.api.SeriesDefinition;
import ch.agent.crnickl.api.Surrogate;
import ch.agent.crnickl.api.UpdatableChronicle;
import ch.agent.crnickl.api.UpdatableProperty;
import ch.agent.crnickl.api.UpdatableSchema;
import ch.agent.crnickl.api.UpdatableSeries;
import ch.agent.crnickl.api.UpdatableValueType;
import ch.agent.crnickl.api.UpdateEventOperation;
import ch.agent.crnickl.api.ValueType;
import ch.agent.crnickl.impl.DatabaseBackend;
import ch.agent.crnickl.impl.DatabaseBackendImpl;
import ch.agent.crnickl.impl.SchemaUpdatePolicy;
import ch.agent.crnickl.impl.UpdateEventImpl;
import ch.agent.crnickl.impl.UpdateEventPublisherImpl;

/**
 * A JDBC implementation of {@link DatabaseBackendImpl}. 
 * 
 * @author Jean-Paul Vetterli
 */
public class MongoDatabase extends DatabaseBackendImpl {

	public static final String FLD_ID = "_id";
	
	public static final String COLL_CHRON = "CHRON";
	public static final String FLD_CHRON_NAME = "name";
	public static final String FLD_CHRON_DESC = "desc";
	public static final String FLD_CHRON_PARENT = "parent";
	public static final String FLD_CHRON_SCHEMA = "schema";
	
	public static final String COLL_SER = "SER";
	public static final String FLD_SER_CHRON = "chron";
	public static final String FLD_SER_NUM = "number";
	public static final String FLD_SER_FIRST = "first";
	public static final String FLD_SER_LAST = "last";
	public static final String FLD_SER_VALUES = "values";
	
	public static final String COLL_ATTR = "ATTR";
	public static final String FLD_ATTR_CHRON = "chron";
	public static final String FLD_ATTR_PROP = "prop";
	public static final String FLD_ATTR_VALUE = "val";
	public static final String FLD_ATTR_DESC = "descr";
	
	public static final String COLL_PROP = "PROP";
	public static final String FLD_PROP_NAME = "name";
	public static final String FLD_PROP_VT = "type";
	public static final String FLD_PROP_INDEXED = "indexed";
	
	public static final String COLL_SCHEMA = "SCH";
	public static final String FLD_SCHEMA_NAME = "name";
	public static final String FLD_SCHEMA_BASE = "base";
	public static final String FLD_SCHEMA_ATTRIBS = "attribs";
	public static final String FLD_SCHEMA_SERIES = "series";
	
	public static final String FLD_ATTRIBDEF_NUM = "num";
	public static final String FLD_ATTRIBDEF_PROP = "prop";
	public static final String FLD_ATTRIBDEF_VAL = "val";
	public static final String FLD_ATTRIBDEF_ERASING = "erasing";
	
	public static final String FLD_SERIESDEF_NUM = "num";
	public static final String FLD_SERIESDEF_DESC = "desc";
	public static final String FLD_SERIESDEF_ATTRIBS = "attribs";
	public static final String FLD_SERIESDEF_ERASING = "erasing";
	
	public static final String COLL_VT = "VT";
	public static final String FLD_VT_NAME = "name";
	public static final String FLD_VT_TYPE = "type";
	public static final String FLD_VT_VALUES = "values";

	/** 
	 * The name of the external parameter specifying the waiting delay range 
	 * for dangerous updates. The parameter value is a range of milliseconds
	 * specified with two numbers separated by a hyphen. The default range is
	 * {@link #DB_PARAM_IntInt_WAITING_DELAY_RANGE_DEFAULT}.
	 */
	public static final String DB_PARAM_IntInt_WAITING_DELAY_RANGE = "dbWaitingDelayRange";
	public static final String DB_PARAM_IntInt_WAITING_DELAY_RANGE_DEFAULT = "3000-6000";
	
	/*
	 * TODO: investigate pulling interfaces and some for ReadMethodsForFoo and
	 * WriteMethodsForBar into the "impl" layer. Is the separation in read and
	 * write methods still meaningful? Why not simply "access methods"?
	 */
	
	private ReadMethodsForChroniclesAndSeries esRMethods;
	private WriteMethodsForChroniclesAndSeries esWMethods;
	private ReadMethodsForValueType vtRMethods;
	private WriteMethodsForValueType vtWMethods;
	private ReadMethodsForProperty pRMethods;
	private WriteMethodsForProperty pWMethods;
	private ReadMethodsForSchema sRMethods;
	private WriteMethodsForSchema sWMethods;
	private MongoDB mongoDB;
	private MongoDBSchemaUpdatePolicy msup;
	
	private static Random random;
	private int minDelay = 3000;
	private int maxDelay = 6000;
		
	/**
	 * Construct a {@link DatabaseBackend}.
	 * 
	 * @param name the name of the database
	 */
	public MongoDatabase(String name) {
		super(name);
	}
	
	@Override
	protected boolean isChronicleUpdatePolicyExtensionAllowed() {
		/*
		 * Completely impossible to undo work without rollback,
		 * so just forbid extensions.
		 */
		return false;
	}



	@Override
	public void configure(DatabaseConfiguration configuration) throws T2DBException {
		super.configure(configuration);
		new MongoDB(this, configuration);
		setAccessMethods(ValueType.StandardValueType.NUMBER.name(), new AccessMethodsForNumber());

		String parameter = configuration.getParameter(DB_PARAM_IntInt_WAITING_DELAY_RANGE, false);
		if (parameter == null)
			parameter = DB_PARAM_IntInt_WAITING_DELAY_RANGE_DEFAULT;
		try {
			String[] bounds = parameter.split("-");
			switch(bounds.length) {
			case 1: 
				minDelay = new Integer(bounds[0]);
				maxDelay = minDelay;
				break;
			case 2: 
				minDelay = new Integer(bounds[0]);
				maxDelay = new Integer(bounds[1]);
				break;
			default:
				throw new IllegalArgumentException("min[-max] ?");
			}
			if (minDelay > maxDelay)
				throw new IllegalArgumentException("min<=max ?");
		} catch (Exception e) {
			throw T2DBMsg.exception(e, D.D00108, DB_PARAM_IntInt_WAITING_DELAY_RANGE, parameter);
		}
	}
	
	@Override
	public SchemaUpdatePolicy getSchemaUpdatePolicy() {
		if (msup == null)
			msup = new MongoDBSchemaUpdatePolicy(this);
		return msup;
	}

	@Override
	public DBObjectId makeDBObjectId(Object object) throws T2DBException {
		return new MongoDBObjectId(object);
	}
	
	public MongoDB getMongoDB() {
		if (mongoDB == null)
			mongoDB = MongoDB.getInstance();
		return mongoDB;
	}
	
	/**
	 * Sleep a number of milliseconds before trying to detect violations of
	 * referential integrity. Return true if there was no
	 * {@link InterruptedException} else return false. This is a random number
	 * in the range specified with the the
	 * {@link #DB_PARAM_IntInt_WAITING_DELAY_RANGE} configuration parameter.
	 * 

* A update is said to be dangerous when there is a risk of leaving the * database in a inconsistent state. The underlying cause is lack of support * for transactions or referential integrity. Example a.b == b._id and b is * deleted. The following pseudo code shows how dangerous updates are * performed.

* *
	 * 
	 * count references of document
	 * if (count > 0)
	 *     fail
	 * memo := get prior state of document to be updated
	 * update document
	 * sleep()
	 * count references again
	 * if (count > 0) 
	 * do
	 *     restore document to its prior state using memo
	 *     fail
	 * done
	 * 
	 * 
* *
* @throws InterruptedException */ public void sleep() throws InterruptedException { int delay = maxDelay - minDelay; if (delay > 0 && random == null) random = new Random(); int millis = minDelay + (delay > 0 ? random.nextInt(maxDelay - minDelay) : 0); Thread.sleep(millis); } /*** manage back end store ***/ @Override public void commit() throws T2DBException { ((UpdateEventPublisherImpl)getUpdateEventPublisher()).release(); } @Override public void rollback() throws T2DBException { // ignore silently } /*** Chronicle and Series ***/ /** * Return the object providing read methods for chronicles and series. * * @return the object providing read methods for chronicles and series */ protected ReadMethodsForChroniclesAndSeries getReadMethodsForChronicleAndSeries() { if (esRMethods == null) esRMethods = new ReadMethodsForChroniclesAndSeries(); return esRMethods; } /** * Return the object providing write methods for chronicles and series. * * @return the object providing write methods for chronicles and series */ protected WriteMethodsForChroniclesAndSeries getWriteMethodsForChroniclesAndSeries() { if (esWMethods == null) esWMethods = new WriteMethodsForChroniclesAndSeries(); return esWMethods; } @Override public void create(UpdatableChronicle entity) throws T2DBException { getWriteMethodsForChroniclesAndSeries().createChronicle(entity); publish(new UpdateEventImpl(UpdateEventOperation.CREATE, entity).withComment(entity.getDescription(false))); } @Override public void update(UpdatableChronicle entity) throws T2DBException { getWriteMethodsForChroniclesAndSeries().updateChronicle(entity, getChronicleUpdatePolicy()); publish(new UpdateEventImpl(UpdateEventOperation.MODIFY, entity)); } @Override public void deleteAttributeValue(UpdatableChronicle entity, AttributeDefinition def) throws T2DBException { getWriteMethodsForChroniclesAndSeries().deleteAttribute(entity, def); publish(new UpdateEventImpl(UpdateEventOperation.MODIFY, entity).withComment("delete attribute #" + def.getNumber())); } @Override public void update(UpdatableChronicle entity, AttributeDefinition def, String value, String description) throws T2DBException { getWriteMethodsForChroniclesAndSeries().updateAttribute(entity, def, value, description); publish(new UpdateEventImpl(UpdateEventOperation.MODIFY, entity).withComment(String.format("%s=%s", def.getProperty().getName(), value))); } @Override public void deleteChronicle(UpdatableChronicle entity) throws T2DBException { getWriteMethodsForChroniclesAndSeries().deleteChronicle(entity, getChronicleUpdatePolicy()); // pass name and description as comment, because they are lost when logger gets them String comment = getNamingPolicy().joinValueAndDescription(entity.getName(true), entity.getDescription(false)); publish(new UpdateEventImpl(UpdateEventOperation.DELETE, entity).withComment(comment)); } @Override public void create(UpdatableSeries series) throws T2DBException { getWriteMethodsForChroniclesAndSeries().createSeries(series); publish(new UpdateEventImpl(UpdateEventOperation.CREATE, series)); } @Override public void deleteSeries(UpdatableSeries series) throws T2DBException { getWriteMethodsForChroniclesAndSeries().deleteSeries(series, getChronicleUpdatePolicy()); String comment = getNamingPolicy().joinValueAndDescription(series.getName(true), series.getDescription(false)); publish(new UpdateEventImpl(UpdateEventOperation.DELETE, series).withComment(comment)); } @Override public Chronicle getChronicle(Chronicle chronicle) throws T2DBException { return getReadMethodsForChronicleAndSeries().getChronicle(chronicle.getSurrogate()); } @Override public Chronicle getChronicleOrNull(Chronicle parent, String simpleName) throws T2DBException { return getReadMethodsForChronicleAndSeries().getChronicleOrNull(parent, simpleName); } @Override public Collection getChroniclesByParent(Chronicle parent) throws T2DBException { return getReadMethodsForChronicleAndSeries().getChroniclesByParent(parent); } @Override public List getChroniclesByAttributeValue(Property property, T value, int maxSize) throws T2DBException { return getReadMethodsForChronicleAndSeries().getChroniclesByAttributeValue(property, value, maxSize); } @Override public boolean getAttributeValue(List chronicles, Attribute attribute) throws T2DBException { return getReadMethodsForChronicleAndSeries().getAttributeValue(chronicles, attribute); } @Override public Series[] getSeries(Chronicle chronicle, String[] names, int[] numbers) throws T2DBException { return getReadMethodsForChronicleAndSeries().getSeries(chronicle, names, numbers); } @Override public Series getSeries(Surrogate surrogate) throws T2DBException { checkSurrogate(surrogate, DBObjectType.SERIES); Series series = getReadMethodsForChronicleAndSeries().getSeries(surrogate); if (series == null) throw T2DBMsg.exception(E.E50104, surrogate.toString()); return series; } /*** Property ***/ /** * Return the object providing read methods for properties. * * @return the object providing read methods for properties */ protected ReadMethodsForProperty getReadMethodsForProperty() { if (pRMethods == null) pRMethods = new ReadMethodsForProperty(); return pRMethods; } /** * Return the object providing write methods for properties. * * @return the object providing write methods for properties */ protected WriteMethodsForProperty getWriteMethodsForProperty() { if (pWMethods == null) pWMethods = new WriteMethodsForProperty(); return pWMethods; } @Override public Collection> getProperties(String pattern) throws T2DBException { return getReadMethodsForProperty().getProperties(this, pattern); } @Override public Property getProperty(Surrogate surrogate) throws T2DBException { checkSurrogate(surrogate, DBObjectType.PROPERTY); Property vt = getReadMethodsForProperty().getProperty(surrogate); if (vt == null) throw T2DBMsg.exception(E.E20109, surrogate.toString()); return vt; } @Override public Property getProperty(String name) throws T2DBException { return getReadMethodsForProperty().getProperty(this, name); } @Override public void create(UpdatableProperty property) throws T2DBException { getWriteMethodsForProperty().createProperty(property); publish(new UpdateEventImpl(UpdateEventOperation.CREATE, property)); } @Override public void deleteProperty(UpdatableProperty property) throws T2DBException { getWriteMethodsForProperty().deleteProperty(property, getSchemaUpdatePolicy()); String comment = property.getName(); publish(new UpdateEventImpl(UpdateEventOperation.DELETE, property).withComment(comment)); } @Override public void update(UpdatableProperty property) throws T2DBException { getWriteMethodsForProperty().updateProperty(property, getSchemaUpdatePolicy()); publish(new UpdateEventImpl(UpdateEventOperation.MODIFY, property)); } /*** ValueType ***/ /** * Return the object providing read methods for value types. * * @return the object providing read methods for value types */ protected ReadMethodsForValueType getReadMethodsForValueType() { if (vtRMethods == null) vtRMethods = new ReadMethodsForValueType(); return vtRMethods; } /** * Return the object providing write methods for value types. * * @return the object providing write methods for value types */ protected WriteMethodsForValueType getWriteMethodsForValueType() { if (vtWMethods == null) vtWMethods = new WriteMethodsForValueType(); return vtWMethods; } @Override public Collection> getValueTypes(String pattern) throws T2DBException { return getReadMethodsForValueType().getValueTypes(this, pattern); } @Override public ValueType getValueType(String name) throws T2DBException { ValueType vt = getReadMethodsForValueType().getValueType(this, name); if (vt == null) throw T2DBMsg.exception(E.E10109, name); return vt; } @Override public ValueType getValueType(Surrogate surrogate) throws T2DBException { checkSurrogate(surrogate, DBObjectType.VALUE_TYPE); ValueType vt = getReadMethodsForValueType().getValueType(surrogate); if (vt == null) throw T2DBMsg.exception(E.E10110, surrogate.toString()); return vt; } @Override public void create(UpdatableValueType valueType) throws T2DBException { getWriteMethodsForValueType().createValueType(valueType); publish(new UpdateEventImpl(UpdateEventOperation.CREATE, valueType)); } @Override public void deleteValueType(UpdatableValueType valueType) throws T2DBException { getWriteMethodsForValueType().deleteValueType(valueType, getSchemaUpdatePolicy()); String comment = valueType.getName(); publish(new UpdateEventImpl(UpdateEventOperation.DELETE, valueType).withComment(comment)); } @Override public void update(UpdatableValueType valueType) throws T2DBException { getWriteMethodsForValueType().updateValueType(valueType, getSchemaUpdatePolicy()); publish(new UpdateEventImpl(UpdateEventOperation.MODIFY, valueType)); } /*** Schemas ***/ /** * Return the object providing read methods for schemas. * * @return the object providing read methods for schemass */ protected ReadMethodsForSchema getReadMethodsForSchema() { if (sRMethods == null) sRMethods = new ReadMethodsForSchema(); return sRMethods; } /** * Return the object providing write methods for schemas. * * @return the object providing write methods for schemas */ protected WriteMethodsForSchema getWriteMethodsForSchema() { if (sWMethods == null) sWMethods = new WriteMethodsForSchema(); return sWMethods; } @Override public Collection getSchemaSurrogates(String pattern) throws T2DBException { return getReadMethodsForSchema().getSchemaSurrogateList(this, pattern); } @Override public UpdatableSchema getUpdatableSchema(Surrogate surrogate) throws T2DBException { UpdatableSchema schema = getReadMethodsForSchema().getSchema(surrogate); if (schema == null) throw T2DBMsg.exception(E.E30109, surrogate.toString()); return schema; } @Override public void create(UpdatableSchema schema) throws T2DBException { getWriteMethodsForSchema().createSchema(schema); publish(new UpdateEventImpl(UpdateEventOperation.CREATE, schema)); } @Override public void update(UpdatableSchema schema) throws T2DBException { getWriteMethodsForSchema().updateSchema(schema, getSchemaUpdatePolicy()); ((UpdateEventPublisherImpl)getUpdateEventPublisher()).publish(new UpdateEventImpl(UpdateEventOperation.MODIFY, schema), false); } @Override public void deleteSchema(UpdatableSchema schema) throws T2DBException { getWriteMethodsForSchema().deleteSchema(schema, getSchemaUpdatePolicy()); String comment = schema.getName(); publish(new UpdateEventImpl(UpdateEventOperation.DELETE, schema).withComment(comment)); } @Override public Surrogate findChronicle(Schema schema) throws T2DBException { return getWriteMethodsForSchema().findChronicle(schema); } @Override public Surrogate findChronicle(Property property, Schema schema) throws T2DBException { return getWriteMethodsForSchema().findChronicle(property, schema); } @Override public Surrogate findChronicle(SeriesDefinition ss, Schema schema) throws T2DBException { return getWriteMethodsForSchema().findChronicle(ss, schema); } @Override public String toString() { return MongoDB.getInstance().toString(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy