/** * Copyright (C) 2012 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.master.user.impl; import java.util.Collection; import java.util.List; import java.util.Locale; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.joda.beans.Bean; import org.joda.beans.JodaBeanUtils; import org.threeten.bp.Instant; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableList; import com.opengamma.DataDuplicationException; import com.opengamma.DataNotFoundException; import com.opengamma.DataVersionException; import com.opengamma.core.change.ChangeManager; import com.opengamma.core.change.ChangeType; import com.opengamma.id.MutableUniqueIdentifiable; import com.opengamma.id.ObjectId; import com.opengamma.id.UniqueId; import com.opengamma.id.UniqueIdentifiable; import com.opengamma.master.user.HistoryEvent; import com.opengamma.util.ArgumentChecker; /** * Abstract base providing a simple, in-memory master. * <p> * This master does not support versioning. */ abstract class AbstractInMemoryMaster<T extends UniqueIdentifiable & MutableUniqueIdentifiable> { // see UserMaster and RoleMaster for method descriptions /** * The map of object identifier by name. */ private final ConcurrentMap<String, ObjectId> _names = new ConcurrentHashMap<>(); /** * The main map of objects. */ private final ConcurrentMap<ObjectId, T> _objects = new ConcurrentHashMap<>(); /** * The object description for errors. */ private final String _objectDescription; /** * The removed marker object. */ private final T _removed; /** * The supplied of identifiers. */ private final Supplier<ObjectId> _objectIdSupplier; /** * The change manager. */ private final ChangeManager _changeManager; /** * Creates an instance. * * @param objectDescription the object description for error messages, not null * @param removed the placeholder object for removing, not null * @param objectIdSupplier the supplier of object identifiers, not null * @param changeManager the change manager, not null */ AbstractInMemoryMaster(String objectDescription, T removed, Supplier<ObjectId> objectIdSupplier, final ChangeManager changeManager) { ArgumentChecker.notNull(objectDescription, "objectDescription"); ArgumentChecker.notNull(removed, "removed"); ArgumentChecker.notNull(objectIdSupplier, "objectIdSupplier"); ArgumentChecker.notNull(changeManager, "changeManager"); _objectDescription = objectDescription; _removed = removed; _objectIdSupplier = objectIdSupplier; _changeManager = changeManager; } //------------------------------------------------------------------------- /** * Gets the stored values, not to be altered. * * @return the values, not null */ Collection<T> getStoredValues() { return _objects.values(); } /** * Gets the change manager. * * @return the change manager, not null */ ChangeManager getChangeManager() { return _changeManager; } /** * Extracts the name from the object. * * @param object the object, not null * @return the name, not null */ abstract String extractName(T object); //------------------------------------------------------------------------- String caseInsensitive(String name) { return name.toLowerCase(Locale.ROOT); } @SuppressWarnings("unchecked") T clone(final T original) { return (T) JodaBeanUtils.clone((Bean) original); } boolean nameExists(String name) { ArgumentChecker.notNull(name, "name"); return _names.containsKey(caseInsensitive(name)); } T getByName(String name) { ArgumentChecker.notNull(name, "name"); T stored = getByName0(name); return clone(stored); } T getByName0(String name) { ObjectId objectId = _names.get(caseInsensitive(name)); if (objectId == null) { throw new DataNotFoundException(_objectDescription + " name not found: " + name); } return getById0(objectId); } T getById(ObjectId objectId) { ArgumentChecker.notNull(objectId, "objectId"); final T stored = getById0(objectId); return clone(stored); } T getById0(ObjectId objectId) { final T stored = _objects.get(objectId); if (stored == null || stored == _removed) { throw new DataNotFoundException(_objectDescription + " identifier not found: " + objectId); } return stored; } //------------------------------------------------------------------------- UniqueId add(T object) { ArgumentChecker.notNull(object, "object"); ObjectId objectId = _objectIdSupplier.get(); UniqueId uniqueId = objectId.atVersion("1"); object = clone(object); object.setUniqueId(uniqueId); String nameKey = caseInsensitive(extractName(object)); synchronized (this) { if (_names.containsKey(nameKey)) { throw new DataDuplicationException(_objectDescription + " already exists: " + extractName(object)); } _objects.put(objectId, object); _names.put(nameKey, objectId); } Instant now = Instant.now(); _changeManager.entityChanged(ChangeType.ADDED, objectId, now, null, now); return uniqueId; } UniqueId update(T object) { ArgumentChecker.notNull(object, "object"); ArgumentChecker.notNull(object.getUniqueId(), "object.uniqueId"); ArgumentChecker.notNull(object.getUniqueId().getVersion(), "object.uniqueId.version"); ObjectId objectId = object.getUniqueId().getObjectId(); String oldVersion = object.getUniqueId().getVersion(); UniqueId newUniqueId = objectId.atVersion(Integer.toString(Integer.parseInt(oldVersion) + 1)); object = clone(object); object.setUniqueId(newUniqueId); String nameKey = caseInsensitive(extractName(object)); synchronized (this) { final T stored = getById0(objectId); if (stored.getUniqueId().getVersion().equals(oldVersion) == false) { throw new DataVersionException("Invalid version, " + _objectDescription + " has already been updated: " + objectId); } if (nameKey.equals(caseInsensitive(extractName(stored)))) { _objects.put(objectId, object); // replace } else { if (_names.containsKey(nameKey)) { throw new DataDuplicationException(_objectDescription + " cannot be renamed, new name already exists: " + extractName(object)); } _objects.put(objectId, object); // replace _names.put(nameKey, objectId); // leave old name to forward to same object } } Instant now = Instant.now(); _changeManager.entityChanged(ChangeType.CHANGED, objectId, now, null, now); return newUniqueId; } UniqueId save(T object) { ArgumentChecker.notNull(object, "object"); if (object.getUniqueId() != null) { return update(object); } else { return add(object); } } //------------------------------------------------------------------------- void removeByName(String name) { ArgumentChecker.notNull(name, "name"); // no need to synchronize as names is append-only ObjectId objectId = _names.get(caseInsensitive(name)); if (objectId == null) { throw new DataNotFoundException(_objectDescription + " name not found: " + name); } removeById(objectId); } void removeById(ObjectId objectId) { ArgumentChecker.notNull(objectId, "objectId"); synchronized (this) { final T stored = _objects.get(objectId); if (stored == null) { throw new DataNotFoundException(_objectDescription + " identifier not found: " + objectId); } else if (stored == _removed) { return; // return quietly to be idempotent } _objects.put(objectId, _removed); // replace } Instant now = Instant.now(); _changeManager.entityChanged(ChangeType.REMOVED, objectId, now, null, now); } //------------------------------------------------------------------------- List<HistoryEvent> eventHistory(ObjectId objectId, String name) { if (objectId != null && _objects.containsKey(objectId) == false) { throw new DataNotFoundException(_objectDescription + " identifier not found: " + objectId); } else if (name != null && _names.containsKey(caseInsensitive(name)) == false) { throw new DataNotFoundException(_objectDescription + " name not found: " + name); } return ImmutableList.of(); } //------------------------------------------------------------------------- @Override public String toString() { return String.format("%s[size=%d]", getClass().getSimpleName(), _objects.size()); } }