OrientDBRepoService.java revision 39a2b1613dd6d8f45a32a9efda82fd0aead4cf43
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * Copyright (c) 2011-2013 ForgeRock AS. All Rights Reserved
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * The contents of this file are subject to the terms
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * of the Common Development and Distribution License
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * (the License). You may not use this file except in
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * compliance with the License.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * You can obtain a copy of the License at
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * See the License for the specific language governing
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * permission and limitations under the License.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * When distributing Covered Code, include this CDDL
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * Header Notice in each file and include the License file
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * If applicable, add the following below the CDDL Header,
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * with the fields enclosed by brackets [] replaced by
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * your own identifying information:
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * "Portions Copyrighted [year] [name of copyright owner]"
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloffpackage org.forgerock.openidm.repo.orientdb.impl;
2c688737c6d30095ecf42f0ad82748deb01939feChad Kienleimport org.apache.felix.scr.annotations.Activate;
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloffimport org.apache.felix.scr.annotations.Component;
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloffimport org.apache.felix.scr.annotations.ConfigurationPolicy;
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloffimport org.apache.felix.scr.annotations.Deactivate;
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloffimport org.apache.felix.scr.annotations.Modified;
2c688737c6d30095ecf42f0ad82748deb01939feChad Kienleimport org.apache.felix.scr.annotations.Properties;
2c688737c6d30095ecf42f0ad82748deb01939feChad Kienleimport org.apache.felix.scr.annotations.Property;
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloffimport org.forgerock.json.fluent.JsonValueException;
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloffimport org.forgerock.json.resource.ActionRequest;
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloffimport org.forgerock.json.resource.BadRequestException;
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloffimport org.forgerock.json.resource.ConflictException;
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloffimport org.forgerock.json.resource.CreateRequest;
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloffimport org.forgerock.json.resource.DeleteRequest;
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloffimport org.forgerock.json.resource.ForbiddenException;
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloffimport org.forgerock.json.resource.InternalServerErrorException;
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloffimport org.forgerock.json.resource.NotFoundException;
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloffimport org.forgerock.json.resource.NotSupportedException;
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloffimport org.forgerock.json.resource.PreconditionFailedException;
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloffimport org.forgerock.json.resource.QueryResultHandler;
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloffimport org.forgerock.json.resource.RequestHandler;
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloffimport org.forgerock.json.resource.ResourceException;
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloffimport org.forgerock.json.resource.ResultHandler;
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloffimport org.forgerock.json.resource.ServerContext;
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloffimport org.forgerock.json.resource.UpdateRequest;
2c688737c6d30095ecf42f0ad82748deb01939feChad Kienleimport org.forgerock.openidm.config.enhanced.EnhancedConfig;
2c688737c6d30095ecf42f0ad82748deb01939feChad Kienleimport org.forgerock.openidm.config.enhanced.JSONEnhancedConfig;
2c688737c6d30095ecf42f0ad82748deb01939feChad Kienleimport org.forgerock.openidm.core.IdentityServer;
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloffimport org.forgerock.openidm.core.ServerConstants;
2c688737c6d30095ecf42f0ad82748deb01939feChad Kienleimport org.forgerock.openidm.repo.QueryConstants;
2c688737c6d30095ecf42f0ad82748deb01939feChad Kienleimport org.forgerock.openidm.repo.RepoBootService;
2c688737c6d30095ecf42f0ad82748deb01939feChad Kienleimport org.forgerock.openidm.repo.RepositoryService;
2c688737c6d30095ecf42f0ad82748deb01939feChad Kienleimport org.forgerock.openidm.repo.orientdb.impl.query.PredefinedQueries;
2c688737c6d30095ecf42f0ad82748deb01939feChad Kienleimport org.forgerock.openidm.repo.orientdb.impl.query.Queries;
2c688737c6d30095ecf42f0ad82748deb01939feChad Kienleimport org.osgi.service.component.ComponentContext;
2c688737c6d30095ecf42f0ad82748deb01939feChad Kienleimport com.orientechnologies.orient.core.db.document.ODatabaseDocumentPool;
2c688737c6d30095ecf42f0ad82748deb01939feChad Kienleimport com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx;
2c688737c6d30095ecf42f0ad82748deb01939feChad Kienleimport com.orientechnologies.orient.core.exception.OConcurrentModificationException;
2c688737c6d30095ecf42f0ad82748deb01939feChad Kienleimport com.orientechnologies.orient.core.exception.ODatabaseException;
2c688737c6d30095ecf42f0ad82748deb01939feChad Kienleimport com.orientechnologies.orient.core.index.OIndexException;
2c688737c6d30095ecf42f0ad82748deb01939feChad Kienleimport com.orientechnologies.orient.core.record.impl.ODocument;
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * Repository service implementation using OrientDB
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @author aegloff
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff@Component(name = OrientDBRepoService.PID, immediate=true, policy=ConfigurationPolicy.REQUIRE, enabled=true)
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff@Service (value = {RepositoryService.class, RequestHandler.class}) // Omit the RepoBootService interface from the managed service
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff @Property(name = "service.description", value = "Repository Service using OrientDB"),
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff @Property(name = "service.vendor", value = ServerConstants.SERVER_VENDOR_NAME),
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff @Property(name = ServerConstants.ROUTER_PREFIX, value = "/repo/*") }) // "/repo/{partition}*") })
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloffpublic class OrientDBRepoService implements RequestHandler, RepositoryService, RepoBootService {
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff final static Logger logger = LoggerFactory.getLogger(OrientDBRepoService.class);
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff public static final String PID = "org.forgerock.openidm.repo.orientdb";
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff // Keys in the JSON configuration
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff public static final String CONFIG_QUERIES = "queries";
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff public static final String CONFIG_DB_URL = "dbUrl";
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff public static final String CONFIG_USER = "user";
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff public static final String CONFIG_PASSWORD = "password";
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff public static final String CONFIG_DB_STRUCTURE = "dbStructure";
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff public static final String CONFIG_ORIENTDB_CLASS = "orientdbClass";
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff public static final String CONFIG_INDEX = "index";
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff public static final String CONFIG_PROPERTY_NAME = "propertyName";
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff public static final String CONFIG_PROPERTY_NAMES = "propertyNames";
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff public static final String CONFIG_PROPERTY_TYPE = "propertyType";
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff public static final String CONFIG_INDEX_TYPE = "indexType";
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff // Current configuration
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff // TODO: evaluate use of Guice instead
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff PredefinedQueries predefinedQueries = new PredefinedQueries();
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff EnhancedConfig enhancedConfig = new JSONEnhancedConfig();
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff public void handleRead(final ServerContext context, final ReadRequest request,
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff public void handleCreate(final ServerContext context, final CreateRequest request,
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff public void handleUpdate(ServerContext context, UpdateRequest request,
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff public void handleDelete(final ServerContext context, final DeleteRequest request,
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff public void handleQuery(final ServerContext context, final QueryRequest request,
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * The object will contain metadata properties, including object identifier
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * {@code _id}, and object version {@code _rev} to enable optimistic
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * concurrency supported by OrientDB and OpenIDM.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @param request
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * the identifier of the object to retrieve from the object set.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @throws NotFoundException
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * if the specified object could not be found.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @throws ForbiddenException
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * if access to the object is forbidden.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @throws BadRequestException
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * if the passed identifier is invalid
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @return the requested object.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff public void handleRead2(final ServerContext context, final ReadRequest request,
bdcef189d83ab13d06dde11499a03b16cdb5b432Chad Kienle final ResultHandler<Resource> handler) throws ResourceException {
bdcef189d83ab13d06dde11499a03b16cdb5b432Chad Kienle public Resource read(ReadRequest request) throws ResourceException {
39a2b1613dd6d8f45a32a9efda82fd0aead4cf43Brendan Mmiller if (request.getResourceNameObject().size() < 2) {
39a2b1613dd6d8f45a32a9efda82fd0aead4cf43Brendan Mmiller throw new NotFoundException("The object identifier did not include sufficient information to determine the object type and identifier of the object to read: " + request.getResourceName());
39a2b1613dd6d8f45a32a9efda82fd0aead4cf43Brendan Mmiller final String type = request.getResourceNameObject().parent().toString();
39a2b1613dd6d8f45a32a9efda82fd0aead4cf43Brendan Mmiller final String localId = request.getResourceNameObject().leaf();
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff ODocument doc = predefinedQueries.getByID(localId, type, db);
39a2b1613dd6d8f45a32a9efda82fd0aead4cf43Brendan Mmiller throw new NotFoundException("Object " + localId + " not found in " + type);
39a2b1613dd6d8f45a32a9efda82fd0aead4cf43Brendan Mmiller logger.trace("Completed get for id: {} result: {}", request.getResourceName(), result);
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * Creates a new object in the object set.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * This method sets the {@code _id} property to the assigned identifier for the object,
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * and the {@code _rev} property to the revised object version (For optimistic concurrency)
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @param context
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * the client-generated identifier to use, or {@code null} if
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * server-generated identifier is requested.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @param request
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * the contents of the object to create in the object set.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @throws NotFoundException
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * if the specified id could not be resolved.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @throws ForbiddenException
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * if access to the object or object set is forbidden.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @throws ConflictException
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * if an object with the same ID already exists.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff public void handleCreate2(final ServerContext context, final CreateRequest request,
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff final ResultHandler<Resource> handler) throws ResourceException {
bdcef189d83ab13d06dde11499a03b16cdb5b432Chad Kienle public Resource create(CreateRequest request) throws ResourceException {
39a2b1613dd6d8f45a32a9efda82fd0aead4cf43Brendan Mmiller if (request.getResourceNameObject().isEmpty()) {
39a2b1613dd6d8f45a32a9efda82fd0aead4cf43Brendan Mmiller throw new NotFoundException("The object identifier did not include sufficient information to determine the object type: " + request.getResourceName());
39a2b1613dd6d8f45a32a9efda82fd0aead4cf43Brendan Mmiller final String type = request.getResourceName();
39a2b1613dd6d8f45a32a9efda82fd0aead4cf43Brendan Mmiller // TODO: should CREST support server side generation of ID itself?
39a2b1613dd6d8f45a32a9efda82fd0aead4cf43Brendan Mmiller final String localId = (request.getNewResourceId() == null || "".equals(request.getNewResourceId()))
39a2b1613dd6d8f45a32a9efda82fd0aead4cf43Brendan Mmiller ? UUID.randomUUID().toString() // Generate ID server side.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff // Used currently for logging
39a2b1613dd6d8f45a32a9efda82fd0aead4cf43Brendan Mmiller String fullId = request.getResourceName() + "/" + localId;
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff String orientClassName = typeToOrientClassName(type);
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff // Rather than using MVCC for insert, rely on primary key uniqueness constraints to detect duplicate create
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff ODocument newDoc = DocumentUtil.toDocument(obj, null, db, orientClassName);
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff logger.trace("Created doc for id: {} to save {}", fullId, newDoc);
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff obj.put(DocumentUtil.TAG_REV, Integer.toString(newDoc.getVersion()));
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff logger.debug("Completed create for id: {} revision: {}", fullId, newDoc.getVersion());
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff logger.trace("Create payload for id: {} doc: {}", fullId, newDoc);
39a2b1613dd6d8f45a32a9efda82fd0aead4cf43Brendan Mmiller return new Resource(obj.get(DocumentUtil.TAG_ID).asString(), obj.get(DocumentUtil.TAG_REV).asString(), obj);
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff // Because the OpenIDM ID is defined as unique, duplicate inserts must fail
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff throw new PreconditionFailedException("Create rejected as Object with same ID already exists. " + ex.getMessage(), ex);
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff } catch (com.orientechnologies.orient.core.exception.ODatabaseException ex) {
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff // Because the OpenIDM ID is defined as unique, duplicate inserts must fail.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff // OrientDB may wrap the IndexException root cause.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff throw new PreconditionFailedException("Create rejected as Object with same ID already exists and was detected. "
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * Updates the specified object in the object set.
39a2b1613dd6d8f45a32a9efda82fd0aead4cf43Brendan Mmiller * This implementation does not require MVCC and uses the current revision if no revision
2c688737c6d30095ecf42f0ad82748deb01939feChad Kienle * is specified in the request.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * If successful, this method updates metadata properties within the passed object,
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * including: a new {@code _rev} value for the revised object's version
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @param fullId the identifier of the object to be put, or {@code null} to request a generated identifier.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @param rev the version of the object to update; or {@code null} if not provided.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @param obj the contents of the object to put in the object set.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @throws ConflictException if version is required but is {@code null}.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @throws ForbiddenException if access to the object is forbidden.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @throws NotFoundException if the specified object could not be found.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @throws PreconditionFailedException if version did not match the existing object in the set.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @throws BadRequestException if the passed identifier is invalid
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff public void handleUpdate2(ServerContext context, UpdateRequest request,
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff ResultHandler<Resource> handler) throws ResourceException {
bdcef189d83ab13d06dde11499a03b16cdb5b432Chad Kienle public Resource update(UpdateRequest request) throws ResourceException {
39a2b1613dd6d8f45a32a9efda82fd0aead4cf43Brendan Mmiller if (request.getResourceNameObject().size() < 2) {
39a2b1613dd6d8f45a32a9efda82fd0aead4cf43Brendan Mmiller throw new NotFoundException("The object identifier did not include sufficient information to determine the object type and identifier of the object to update: " + request.getResourceName());
39a2b1613dd6d8f45a32a9efda82fd0aead4cf43Brendan Mmiller final String type = request.getResourceNameObject().parent().toString();
39a2b1613dd6d8f45a32a9efda82fd0aead4cf43Brendan Mmiller final String localId = request.getResourceNameObject().leaf();
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff String orientClassName = typeToOrientClassName(type);
39a2b1613dd6d8f45a32a9efda82fd0aead4cf43Brendan Mmiller if (request.getRevision() != null && !"".equals(request.getRevision())) {
39a2b1613dd6d8f45a32a9efda82fd0aead4cf43Brendan Mmiller obj.put(DocumentUtil.TAG_REV, request.getRevision());
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff ODocument existingDoc = predefinedQueries.getByID(localId, type, db);
39a2b1613dd6d8f45a32a9efda82fd0aead4cf43Brendan Mmiller throw new NotFoundException("Update on object " + request.getResourceName() + " could not find existing object.");
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff ODocument updatedDoc = DocumentUtil.toDocument(obj, existingDoc, db, orientClassName);
39a2b1613dd6d8f45a32a9efda82fd0aead4cf43Brendan Mmiller logger.trace("Updated doc for id {} to save {}", request.getResourceName(), updatedDoc);
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff obj.put(DocumentUtil.TAG_REV, Integer.toString(updatedDoc.getVersion()));
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff // Set ID to return to caller
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff obj.put(DocumentUtil.TAG_ID, updatedDoc.field(DocumentUtil.ORIENTDB_PRIMARY_KEY));
39a2b1613dd6d8f45a32a9efda82fd0aead4cf43Brendan Mmiller logger.debug("Committed update for id: {} revision: {}", request.getResourceName(), updatedDoc.getVersion());
39a2b1613dd6d8f45a32a9efda82fd0aead4cf43Brendan Mmiller logger.trace("Update payload for id: {} doc: {}", request.getResourceName(), updatedDoc);
bdcef189d83ab13d06dde11499a03b16cdb5b432Chad Kienle return new Resource(obj.get(DocumentUtil.TAG_ID).asString(),
7a2aa1b288f80a14f55d273370840a2a9c0aab85Andi Egloff // Without transaction the concurrent modification exception gets nested instead
7a2aa1b288f80a14f55d273370840a2a9c0aab85Andi Egloff if (isCauseConcurrentModificationException(ex, 10)) {
7a2aa1b288f80a14f55d273370840a2a9c0aab85Andi Egloff "Update rejected as current Object revision is different than expected by caller, the object has changed since retrieval: "
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff throw new PreconditionFailedException("Update rejected as current Object revision is different than expected by caller, the object has changed since retrieval: " + ex.getMessage(), ex);
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * Deletes the specified object from the object set.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * {@inheritDoc}
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @throws NotFoundException
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * if the specified object could not be found.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @throws ForbiddenException
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * if access to the object is forbidden.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @throws ConflictException
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * if version is required but is {@code null}.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @throws PreconditionFailedException
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * if version did not match the existing object in the set.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff public void handleDelete2(final ServerContext context, final DeleteRequest request,
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff final ResultHandler<Resource> handler) throws ResourceException {
bdcef189d83ab13d06dde11499a03b16cdb5b432Chad Kienle public Resource delete(DeleteRequest request) throws ResourceException {
39a2b1613dd6d8f45a32a9efda82fd0aead4cf43Brendan Mmiller if (request.getResourceNameObject().size() < 2) {
39a2b1613dd6d8f45a32a9efda82fd0aead4cf43Brendan Mmiller throw new NotFoundException("The object identifier did not include sufficient information to determine the object type and identifier of the object to update: " + request.getResourceName());
39a2b1613dd6d8f45a32a9efda82fd0aead4cf43Brendan Mmiller if (request.getRevision() == null || "".equals(request.getRevision())) {
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff throw new ConflictException("Object passed into delete does not have revision it expects set.");
39a2b1613dd6d8f45a32a9efda82fd0aead4cf43Brendan Mmiller final String type = request.getResourceNameObject().parent().toString();
39a2b1613dd6d8f45a32a9efda82fd0aead4cf43Brendan Mmiller final String localId = request.getResourceNameObject().leaf();
39a2b1613dd6d8f45a32a9efda82fd0aead4cf43Brendan Mmiller int ver = DocumentUtil.parseVersion(request.getRevision()); // This throws ConflictException if parse fails
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff ODocument existingDoc = predefinedQueries.getByID(localId, type, db);
39a2b1613dd6d8f45a32a9efda82fd0aead4cf43Brendan Mmiller throw new NotFoundException("Object does not exist for delete on: " + request.getResourceName());
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff existingDoc.setVersion(ver); // State the version we expect to delete for MVCC check
39a2b1613dd6d8f45a32a9efda82fd0aead4cf43Brendan Mmiller logger.debug("delete for id succeeded: {} revision: {}", localId, request.getRevision());
bdcef189d83ab13d06dde11499a03b16cdb5b432Chad Kienle return new Resource(localId, null, new JsonValue(null));
7a2aa1b288f80a14f55d273370840a2a9c0aab85Andi Egloff // Without transaction the concurrent modification exception gets nested instead
7a2aa1b288f80a14f55d273370840a2a9c0aab85Andi Egloff if (isCauseConcurrentModificationException(ex, 10)) {
7a2aa1b288f80a14f55d273370840a2a9c0aab85Andi Egloff "Delete rejected as current Object revision is different than expected by caller, the object has changed since retrieval. "
7a2aa1b288f80a14f55d273370840a2a9c0aab85Andi Egloff "Delete rejected as current Object revision is different than expected by caller, the object has changed since retrieval."
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff public void handlePatch(final ServerContext context, final PatchRequest request,
3a5174589eea431f6da6dc1bc3f20e3d358377f8Andi Egloff // TODO: impl
3a5174589eea431f6da6dc1bc3f20e3d358377f8Andi Egloff handler.handleError(new NotSupportedException("Patch not supported yet"));
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff public void handleAction(final ServerContext context, final ActionRequest request,
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff handler.handleError(new NotSupportedException("Action not supported"));
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * Performs the query on the specified object and returns the associated results.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * Queries are parametric; a set of named parameters is provided as the query criteria.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * The query result is a JSON object structure composed of basic Java types.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * The returned map is structured as follow:
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * - The top level map contains meta-data about the query, plus an entry with the actual result records.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * - The <code>QueryConstants</code> defines the map keys, including the result records (QUERY_RESULT)
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @param context
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * identifies the object to query.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @param request
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * the parameters of the query to perform.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @return the query results, which includes meta-data and the result
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * records in JSON object structure format.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @throws NotFoundException
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * if the specified object could not be found.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @throws BadRequestException
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * if the specified params contain invalid arguments, e.g. a
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * query id that is not configured, a query expression that is
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * invalid, or missing query substitution tokens.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @throws ForbiddenException
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * if access to the object or specified query is forbidden.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff public void handleQuery2(final ServerContext context, final QueryRequest request,
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff final QueryResultHandler handler) throws ResourceException {
bdcef189d83ab13d06dde11499a03b16cdb5b432Chad Kienle public List<Resource> query(QueryRequest request) throws ResourceException {
3a5174589eea431f6da6dc1bc3f20e3d358377f8Andi Egloff List<Resource> results = new ArrayList<Resource>();
39a2b1613dd6d8f45a32a9efda82fd0aead4cf43Brendan Mmiller logger.trace("Full id: {} Extracted type: {}", request.getResourceName(), request.getResourceName());
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff // TODO: Statistics is not returned in result anymore
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff // TODO: result is not needed in map form anymore
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff Map<String, Object> result = new HashMap<String, Object>();
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff //List<Map<String, Object>> docs = new ArrayList<Map<String, Object>>();
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff //result.put(QueryConstants.QUERY_RESULT, docs);
39a2b1613dd6d8f45a32a9efda82fd0aead4cf43Brendan Mmiller List<ODocument> queryResult = queries.query(request.getResourceName(), request, db);
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff Map<String, Object> convertedEntry = DocumentUtil.toMap(entry);
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff //docs.add(convertedEntry);
3a5174589eea431f6da6dc1bc3f20e3d358377f8Andi Egloff (String) convertedEntry.get(DocumentUtil.TAG_ID),
3a5174589eea431f6da6dc1bc3f20e3d358377f8Andi Egloff (String) convertedEntry.get(DocumentUtil.TAG_REV),
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff result.put(QueryConstants.STATISTICS_CONVERSION_TIME, Long.valueOf(convEnd-convStart));
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff result.put(QueryConstants.STATISTICS_QUERY_TIME, Long.valueOf(end-start));
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff logger.debug("Query result contains {} records, took {} ms and took {} ms to convert result.",
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff new Object[] {((List) result.get(QueryConstants.QUERY_RESULT)).size(),
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff result.get(QueryConstants.STATISTICS_QUERY_TIME),
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff result.get(QueryConstants.STATISTICS_CONVERSION_TIME)});
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @return A connection from the pool. Call close on the connection when done to return to the pool.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @throws org.forgerock.openidm.objset.InternalServerErrorException
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff ODatabaseDocumentTx getConnection() throws InternalServerErrorException {
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff int maxRetry = 100; // give it up to approx 10 seconds to recover
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff logger.info("Succeeded in acquiring connection from pool in retry attempt {}", retryCount);
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff } catch (com.orientechnologies.orient.core.exception.ORecordNotFoundException ex) {
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff // TODO: remove work-around once OrientDB resolves this condition
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff logger.warn("Failure reported acquiring connection from pool, retried {} times before giving up.", retryCount, ex);
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff "Failure reported acquiring connection from pool, retried " + retryCount + " times before giving up: "
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff logger.info("Pool acquire reported failure, retrying - attempt {}", retryCount);
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff logger.trace("Pool acquire failure detail ", ex);
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff Thread.sleep(100); // Give the DB time to complete what it's doing before retrying
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff // ignore that sleep was interrupted
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff public static String typeToOrientClassName(String type) {
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff //public static String idToOrientClassName(String id) {
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff // String type = getObjectType(id);
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff // return typeToOrientClassName(type);
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * Detect if the root cause of the exception is an index constraint violation
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * This is necessary as the database may wrap this root cause in further exceptions,
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * masking the underlying cause
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @param ex The throwable to check
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @param maxLevels the maximum level of causes to check, avoiding the cost
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * of checking recursiveness
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff private boolean isCauseIndexException(Throwable ex, int maxLevels) {
3a5174589eea431f6da6dc1bc3f20e3d358377f8Andi Egloff return isCauseException (ex, OIndexException.class, maxLevels);
7a2aa1b288f80a14f55d273370840a2a9c0aab85Andi Egloff * Detect if the root cause of the exception is an index constraint violation
7a2aa1b288f80a14f55d273370840a2a9c0aab85Andi Egloff * This is necessary as the database may wrap this root cause in further exceptions,
7a2aa1b288f80a14f55d273370840a2a9c0aab85Andi Egloff * masking the underlying cause
7a2aa1b288f80a14f55d273370840a2a9c0aab85Andi Egloff * @param ex The throwable to check
7a2aa1b288f80a14f55d273370840a2a9c0aab85Andi Egloff * @param maxLevels the maximum level of causes to check, avoiding the cost
7a2aa1b288f80a14f55d273370840a2a9c0aab85Andi Egloff * of checking recursiveness
7a2aa1b288f80a14f55d273370840a2a9c0aab85Andi Egloff private boolean isCauseConcurrentModificationException(Throwable ex, int maxLevels) {
3a5174589eea431f6da6dc1bc3f20e3d358377f8Andi Egloff return isCauseException (ex, OConcurrentModificationException.class, maxLevels);
7a2aa1b288f80a14f55d273370840a2a9c0aab85Andi Egloff * Detect if the root cause of the exception is a specific OrientDB exception
7a2aa1b288f80a14f55d273370840a2a9c0aab85Andi Egloff * This is necessary as the database may wrap this root cause in further exceptions,
7a2aa1b288f80a14f55d273370840a2a9c0aab85Andi Egloff * masking the underlying cause
7a2aa1b288f80a14f55d273370840a2a9c0aab85Andi Egloff * @param ex The throwable to check
7a2aa1b288f80a14f55d273370840a2a9c0aab85Andi Egloff * @param clazz the specific OrientDB exception to check for
7a2aa1b288f80a14f55d273370840a2a9c0aab85Andi Egloff * @param maxLevels the maximum level of causes to check, avoiding the cost
7a2aa1b288f80a14f55d273370840a2a9c0aab85Andi Egloff * of checking recursiveness
7a2aa1b288f80a14f55d273370840a2a9c0aab85Andi Egloff * @return whether the root cause is the specified exception
7a2aa1b288f80a14f55d273370840a2a9c0aab85Andi Egloff private boolean isCauseException(Throwable ex, Class clazz, int maxLevels) {
7a2aa1b288f80a14f55d273370840a2a9c0aab85Andi Egloff return clazz.isInstance(cause) || isCauseException(cause, clazz, maxLevels - 1);
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff return false;
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * Populate and return a repository service that knows how to query and manipulate configuration.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @param repoConfig the bootstrap configuration
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @return the boot repository service. This instance is not managed by SCR and needs to be manually registered.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff static OrientDBRepoService getRepoBootService(Map repoConfig) {
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff OrientDBRepoService bootRepo = new OrientDBRepoService();
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff void activate(ComponentContext compContext) throws Exception {
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff logger.debug("Activating Service with configuration {}", compContext.getProperties());
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff existingConfig = enhancedConfig.getConfigurationAsJson(compContext);
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff logger.warn("Configuration invalid and could not be parsed, can not start OrientDB repository: "
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * Initialize the instnace with the given configuration.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * This can configure managed (DS/SCR) instances, as well as explicitly instantiated
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * (bootstrap) instances.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @param config the configuration
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff Map<String, String> queryMap = (Map<String, String>) map;
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff logger.warn("Configuration invalid, can not start OrientDB repository", ex);
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff pool = DBHelper.getPool(dbURL, user, password, poolMinSize, poolMaxSize, config, true);
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff logger.warn("Initializing database pool failed", ex);
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff File dbFolder = IdentityServer.getFileForWorkingPath("db/openidm");
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff String orientDbFolder = dbFolder.getAbsolutePath();
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff orientDbFolder = orientDbFolder.replace('\\', '/'); // OrientDB does not handle backslashes well
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff return config.get(OrientDBRepoService.CONFIG_DB_URL).defaultTo("local:" + orientDbFolder).asString();
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff return config.get(CONFIG_USER).defaultTo("admin").asString();
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff return config.get(CONFIG_PASSWORD).defaultTo("admin").asString();
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * Adapts a {@code Throwable} to a {@code ResourceException}. If the
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * {@code Throwable} is an JSON {@code JsonValueException} then an
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * appropriate {@code ResourceException} is returned, otherwise an
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * {@code InternalServerErrorException} is returned.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * The {@code Throwable} to be converted.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @return The equivalent resource exception.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff public ResourceException adapt(final Throwable t) {
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff resourceResultCode = ResourceException.VERSION_MISMATCH;
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff } catch (final ResourceException e) {
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff } catch (final JsonValueException e) {
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff resourceResultCode = ResourceException.BAD_REQUEST;
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff resourceResultCode = ResourceException.INTERNAL_ERROR;
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff return ResourceException.getException(resourceResultCode, t.getMessage(), t);
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * Handle an existing activated service getting changed;
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * e.g. configuration changes or dependency changes
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @param compContext THe OSGI component context
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @throws Exception if handling the modified event failed
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff void modified(ComponentContext compContext) throws Exception {
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff logger.debug("Handle repository service modified notification");
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff newConfig = enhancedConfig.getConfigurationAsJson(compContext);
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff logger.warn("Configuration invalid and could not be parsed, can not start OrientDB repository", ex);
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff // If the DB pool settings don't change keep the existing pool
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff logger.info("(Re-)initialize repository with latest configuration.");
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff // If the DB pool settings changed do a more complete re-initialization
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff logger.info("Re-initialize repository with latest configuration - including DB pool setting changes.");
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff logger.debug("Deactivating Service {}", compContext);
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * Cleanup and close the repository