Java tutorial
/* * 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.HashMap; 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.AttributeDefinition; import ch.agent.crnickl.api.Chronicle; import ch.agent.crnickl.api.DBObjectId; import ch.agent.crnickl.api.Database; import ch.agent.crnickl.api.Schema; import ch.agent.crnickl.api.Series; import ch.agent.crnickl.api.Surrogate; import ch.agent.crnickl.api.UpdatableChronicle; import ch.agent.crnickl.api.UpdatableSeries; import ch.agent.crnickl.impl.ChronicleUpdatePolicy; import ch.agent.crnickl.impl.Permission; import com.mongodb.BasicDBObject; import com.mongodb.WriteConcern; /** * A stateless object with methods providing write access to chronicles and * series. * To keep things as simple as possible in a first version, * chronicles and series values are stored separately, as in relational * implementations. Changing this would require a non-negligible part of the * implementation. * */ public class WriteMethodsForChroniclesAndSeries extends ReadMethodsForChroniclesAndSeries { public WriteMethodsForChroniclesAndSeries() { } /** * Create a chronicle in the database. * Throw an exception if the operation cannot be done. * * @param chronicle a chronicle * @throws T2DBException */ public void createChronicle(Chronicle chronicle) throws T2DBException { createChronicle(chronicle, false); } private void createChronicle(Chronicle chronicle, boolean undoDelete) throws T2DBException { Surrogate surrogate = null; Throwable cause = null; try { String name = chronicle.getName(false); chronicle.getSurrogate().getDatabase().getNamingPolicy().checkSimpleName(name, false); // defensive String description = chronicle.getDescription(false); if (description == null) description = ""; Chronicle collection = chronicle.getCollection(); Schema schema = chronicle.getSchema(false); if (collection != null) { check(Permission.MODIFY, collection); if (schema != null && schema.equals(collection.getSchema(true))) schema = null; // don't repeat yourself } if (schema != null) check(Permission.READ, schema); DBObjectId ox = insert(chronicle); surrogate = makeSurrogate(chronicle, ox); } catch (Exception e) { cause = e; } finally { } if (surrogate == null || cause != null) throw T2DBMsg.exception(cause, E.E40109, chronicle.getName(true)); if (!undoDelete) chronicle.getSurrogate().upgrade(surrogate); } /** * Delete a chronicle from the database. Also delete its attribute values * and possibly other dependent objects. The chronicle update policy is * supposed to forbid deleting when there are dependent chronicles or * series, but to allow cascading delete of attribute values. Throw an * exception if the operation cannot be done. * * @param chronicle a chronicle * @param policy a chronicle updating policy * @throws T2DBException */ public void deleteChronicle(UpdatableChronicle chronicle, ChronicleUpdatePolicy policy) throws T2DBException { boolean done = false; Throwable cause = null; Surrogate s = chronicle.getSurrogate(); MongoDatabase db = (MongoDatabase) s.getDatabase(); try { check(Permission.MODIFY, chronicle); policy.willDelete(chronicle); Chronicle original = getChronicle(s); getMongoDB(s).getChronicles().remove(asQuery(s.getId()), WriteConcern.SAFE); db.sleep(); try { policy.willDelete(chronicle); } catch (T2DBException e) { // Oops! referential integrity broken! createChronicle(original, true); throw e; } deleteAttributes(chronicle); done = true; } catch (Exception e) { cause = e; } finally { } if (!done || cause != null) { throw T2DBMsg.exception(cause, E.E40110, chronicle.getName(true)); } } /** * Update a chronicle. Currently only name and description can be updated. * The schema and the parent chronicle cannot be updated. Throw an exception * if the operation cannot be done. * * @param chronicle * a chronicle * @param policy * a chronicle updating policy * @throws T2DBException */ public void updateChronicle(UpdatableChronicle chronicle, ChronicleUpdatePolicy policy) throws T2DBException { String name = chronicle.getName(false); String description = chronicle.getDescription(false); if (description == null) description = ""; try { check(Permission.MODIFY, chronicle); policy.willUpdate(chronicle); getMongoDB(chronicle.getSurrogate()).getChronicles().update( mongoObject(MongoDatabase.FLD_ID, getId(chronicle)), mongoObject(MongoDatabase.FLD_CHRON_NAME, name, MongoDatabase.FLD_CHRON_DESC, description), true, false); } catch (Exception e) { throw T2DBMsg.exception(E.E40111, chronicle.getName(true)); } } /** * Update a chronicle attribute. * Throw an exception if the operation cannot be done. * * @param chronicle a chronicle * @param def an attribute definition * @param value a value * @param description a string * @throws T2DBException */ public void updateAttribute(UpdatableChronicle chronicle, AttributeDefinition<?> def, String value, String description) throws T2DBException { try { check(Permission.MODIFY, chronicle); getMongoDB(chronicle.getSurrogate()).getAttributes() .update(mongoObject(MongoDatabase.FLD_ATTR_CHRON, getId(chronicle), MongoDatabase.FLD_ATTR_PROP, getId(def.getProperty())), mongoObject(MongoDatabase.FLD_ATTR_CHRON, getId(chronicle), MongoDatabase.FLD_ATTR_PROP, getId(def.getProperty()), MongoDatabase.FLD_ATTR_VALUE, value, MongoDatabase.FLD_ATTR_DESC, description), true, false); } catch (Exception e) { throw T2DBMsg.exception(E.E40112, chronicle.getName(true), def.getNumber()); } } /** * Delete an attribute value from a chronicle. * Throw an exception if the operation cannot be done. * * @param chronicle a chronicle * @param def an attribute definition * @throws T2DBException */ public void deleteAttribute(Chronicle chronicle, AttributeDefinition<?> def) throws T2DBException { try { check(Permission.MODIFY, chronicle); getMongoDB(chronicle.getSurrogate()).getAttributes().remove(mongoObject(MongoDatabase.FLD_ATTR_CHRON, getId(chronicle), MongoDatabase.FLD_ATTR_PROP, getId(def.getProperty())), WriteConcern.SAFE); } catch (Exception e) { throw T2DBMsg.exception(E.E40114, chronicle.getName(true), def.getNumber()); } } /** * Delete all attribute values of a chronicle. * Return the list of attributes, in case they need to be recreated. * * @param chronicle * @throws T2DBException */ private void deleteAttributes(Chronicle chronicle) throws T2DBException { Surrogate s = chronicle.getSurrogate(); Database db = s.getDatabase(); try { getMongoDB(db).getAttributes().remove(mongoObject(MongoDatabase.FLD_ATTR_CHRON, getId(s))); } catch (Exception e) { throw T2DBMsg.exception(e, E.E40124, chronicle.getName(true)); } } /** * Create an empty series. Throw an exception if the operation cannot be * done. * * @param series * a series * @throws T2DBException */ public void createSeries(Series<?> series) throws T2DBException { createSeries(series, false); } private void createSeries(Series<?> series, boolean undoDelete) throws T2DBException { Surrogate surrogate = null; Throwable cause = null; try { check(Permission.MODIFY, series.getChronicle()); DBObjectId ox = insert(series); surrogate = makeSurrogate(series, ox); } catch (Exception e) { cause = e; } finally { } if (surrogate == null || cause != null) throw T2DBMsg.exception(cause, E.E50111, series.getName(true)); if (!undoDelete) series.getSurrogate().upgrade(surrogate); } /** * Delete a series. The policy typically forbids to delete a series which is * not empty. Throw an exception if the operation cannot be done. * * @param series * a series * @param policy * a chronicle update policy * @throws T2DBException */ public void deleteSeries(UpdatableSeries<?> series, ChronicleUpdatePolicy policy) throws T2DBException { boolean done = false; Throwable cause = null; Surrogate s = series.getSurrogate(); MongoDatabase db = (MongoDatabase) s.getDatabase(); try { check(Permission.MODIFY, series); policy.willDelete(series); done = policy.deleteSeries(series); Series<?> original = db.getReadMethodsForChronicleAndSeries().getSeries(s); getMongoDB(db).getSeries().remove(asQuery(s.getId()), WriteConcern.SAFE); db.sleep(); try { policy.willDelete(series); } catch (T2DBException e) { // Oops! referential integrity broken! createSeries(original, true); throw e; } done = true; } catch (Exception e) { cause = e; } if (!done || cause != null) throw T2DBMsg.exception(cause, E.E50112, series.getName(true)); } private DBObjectId insert(Chronicle chronicle) throws T2DBException { com.mongodb.BasicDBObject bo = new BasicDBObject(); Surrogate s = chronicle.getSurrogate(); if (!s.inConstruction()) bo.put(MongoDatabase.FLD_ID, getId(chronicle)); // use case: re-creating bo.put(MongoDatabase.FLD_CHRON_NAME, chronicle.getName(false)); bo.put(MongoDatabase.FLD_CHRON_DESC, chronicle.getDescription(false)); bo.put(MongoDatabase.FLD_CHRON_PARENT, getIdOrZero(chronicle.getCollection())); bo.put(MongoDatabase.FLD_CHRON_SCHEMA, getIdOrZero(chronicle.getSchema(false))); getMongoDB(s).getChronicles().insert(bo); ObjectId ox = getObjectId(bo); return new MongoDBObjectId(ox); } private <T> DBObjectId insert(Series<T> series) throws T2DBException { com.mongodb.BasicDBObject bo = new BasicDBObject(); Surrogate s = series.getSurrogate(); if (!s.inConstruction()) bo.put(MongoDatabase.FLD_ID, getId(series)); // use case: re-creating bo.put(MongoDatabase.FLD_SER_CHRON, getIdOrZero(series.getChronicle())); bo.put(MongoDatabase.FLD_SER_NUM, series.getNumber()); bo.put(MongoDatabase.FLD_SER_FIRST, 1); bo.put(MongoDatabase.FLD_SER_LAST, 0); bo.put(MongoDatabase.FLD_SER_VALUES, new HashMap<String, DBObjectId>()); getMongoDB(s).getSeries().insert(bo); ObjectId ox = getObjectId(bo); return new MongoDBObjectId(ox); } }