5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener * Copyright 2011-2015 ForgeRock AS
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;
7b16651ac38dde7780c1272067b1938e275325acJim Mitchenerimport static org.forgerock.json.resource.CountPolicy.EXACT;
7b16651ac38dde7780c1272067b1938e275325acJim Mitchenerimport static org.forgerock.json.resource.CountPolicy.NONE;
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchenerimport static org.forgerock.json.resource.QueryResponse.NO_COUNT;
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchenerimport static org.forgerock.json.resource.Responses.newActionResponse;
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchenerimport static org.forgerock.json.resource.Responses.newQueryResponse;
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchenerimport static org.forgerock.json.resource.Responses.newResourceResponse;
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;
08b028f3cb5252a892956716ce12f4770e69f423Chad Kienleimport org.apache.felix.scr.annotations.Reference;
08b028f3cb5252a892956716ce12f4770e69f423Chad Kienleimport org.apache.felix.scr.annotations.ReferencePolicy;
b84068e6021ec8a830c26c4494f6e335d1f9c0efBrendan Millerimport org.forgerock.openidm.router.IDMConnectionFactory;
21dcdac963f79c098a5ea1a2c5c5e109429c9786Brendan Millerimport org.forgerock.services.context.Context;
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloffimport org.forgerock.json.resource.ActionRequest;
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchenerimport org.forgerock.json.resource.ActionResponse;
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;
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchenerimport org.forgerock.json.resource.QueryResourceHandler;
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchenerimport org.forgerock.json.resource.QueryResponse;
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloffimport org.forgerock.json.resource.RequestHandler;
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchenerimport org.forgerock.json.resource.ResourceResponse;
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloffimport org.forgerock.json.resource.ResourceException;
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloffimport org.forgerock.json.resource.UpdateRequest;
2c688737c6d30095ecf42f0ad82748deb01939feChad Kienleimport org.forgerock.openidm.config.enhanced.EnhancedConfig;
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;
79d12e9411155c9e79c08df9bb6412b2862e6064Brendan Mmillerimport org.forgerock.openidm.repo.orientdb.impl.query.Commands;
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;
cbb34fee4a2f478defcf65ea232dd810b46c0f0bChris Drakeimport com.orientechnologies.orient.core.storage.ORecordDuplicatedException;
812374fe11d8d95123cb0fd35447c43385a90549Chris Drakeimport com.orientechnologies.orient.core.version.OSimpleVersion;
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * Repository service implementation using OrientDB
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";
79d12e9411155c9e79c08df9bb6412b2862e6064Brendan Mmiller public static final String CONFIG_COMMANDS = "commands";
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";
10fed32259a7430282ad1db9194450808ad5d932Jim Mitchener public static final String CONFIG_POOL_MIN_SIZE = "poolMinSize";
10fed32259a7430282ad1db9194450808ad5d932Jim Mitchener public static final String CONFIG_POOL_MAX_SIZE = "poolMaxSize";
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";
7a0b4bab2de703c8967aa0fea836ded4b9805112Brendan Mmiller /** The Connection Factory */
b84068e6021ec8a830c26c4494f6e335d1f9c0efBrendan Miller protected IDMConnectionFactory connectionFactory;
2312a9d8e5342846988edcd629e1ac56a8b5d4a9Brendan Mmiller /** Enhanced configuration service. */
2312a9d8e5342846988edcd629e1ac56a8b5d4a9Brendan Mmiller @Reference(policy = ReferencePolicy.DYNAMIC)
10fed32259a7430282ad1db9194450808ad5d932Jim Mitchener private static final int DEFAULT_POOL_MIN_SIZE = 5;
10fed32259a7430282ad1db9194450808ad5d932Jim Mitchener private static final int DEFAULT_POOL_MAX_SIZE = 20;
08b028f3cb5252a892956716ce12f4770e69f423Chad Kienle // Used to synchronize operations on the DB that require user/password credentials
08b028f3cb5252a892956716ce12f4770e69f423Chad Kienle private static OrientDBRepoService bootRepo = null;
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff // Current configuration
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff // TODO: evaluate use of Guice instead
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff PredefinedQueries predefinedQueries = new PredefinedQueries();
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener public Promise<ResourceResponse, ResourceException> handleRead(final Context context, final ReadRequest request) {
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener public Promise<ResourceResponse, ResourceException> handleCreate(final Context context, final CreateRequest request) {
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener public Promise<ResourceResponse, ResourceException> handleUpdate(final Context context, UpdateRequest request) {
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener public Promise<ResourceResponse, ResourceException> handleDelete(final Context context, final DeleteRequest 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.
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener public ResourceResponse read(ReadRequest request) throws ResourceException {
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener if (request.getResourcePathObject().size() < 2) {
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener throw new NotFoundException("The object identifier did not include sufficient information to determine the object type and identifier of the object to read: " + request.getResourcePath());
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener final String type = request.getResourcePathObject().parent().toString();
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener final String localId = request.getResourcePathObject().leaf();
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff ODocument doc = predefinedQueries.getByID(localId, type, db);
39a2b1613dd6d8f45a32a9efda82fd0aead4cf43Brendan Mmiller throw new NotFoundException("Object " + localId + " not found in " + type);
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener logger.trace("Completed get for id: {} result: {}", request.getResourcePath(), 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 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.
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener public ResourceResponse create(CreateRequest request) throws ResourceException {
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener if (request.getResourcePathObject().isEmpty()) {
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener throw new NotFoundException("The object identifier did not include sufficient information to determine the object type: " + request.getResourcePath());
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener final String type = request.getResourcePath();
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
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener String fullId = request.getResourcePathObject().child(localId).toString();
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);
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener return newResourceResponse(obj.get(DocumentUtil.TAG_ID).asString(), obj.get(DocumentUtil.TAG_REV).asString(), obj);
cbb34fee4a2f478defcf65ea232dd810b46c0f0bChris Drake // Because the OpenIDM ID is defined as unique, duplicate inserts must fail
cbb34fee4a2f478defcf65ea232dd810b46c0f0bChris Drake throw new PreconditionFailedException("Create rejected as Object with same ID already exists. " + ex.getMessage(), ex);
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 // Because the OpenIDM ID is defined as unique, duplicate inserts must fail.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff // OrientDB may wrap the IndexException root cause.
cbb34fee4a2f478defcf65ea232dd810b46c0f0bChris Drake if (isCauseIndexException(ex, 10) || isCauseRecordDuplicatedException(ex, 10)) {
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff throw new PreconditionFailedException("Create rejected as Object with same ID already exists and was detected. "
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener * 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
280f5f217c81e0b90c2b526a8a03849c1371545cBrendan Mmiller * @param request the contents of the object to update
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @throws ConflictException if version is required but is {@code null}.
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff * @throws ForbiddenException if access to the object is forbidden.
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener * @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
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener public ResourceResponse update(UpdateRequest request) throws ResourceException {
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener if (request.getResourcePathObject().size() < 2) {
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener throw new NotFoundException("The object identifier did not include sufficient information to determine the object type and identifier of the object to update: " + request.getResourcePath());
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener final String type = request.getResourcePathObject().parent().toString();
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener final String localId = request.getResourcePathObject().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);
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener throw new NotFoundException("Update on object " + request.getResourcePath() + " could not find existing object.");
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff ODocument updatedDoc = DocumentUtil.toDocument(obj, existingDoc, db, orientClassName);
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener logger.trace("Updated doc for id {} to save {}", request.getResourcePath(), 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));
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener logger.debug("Committed update for id: {} revision: {}", request.getResourcePath(), updatedDoc.getVersion());
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener logger.trace("Update payload for id: {} doc: {}", request.getResourcePath(), updatedDoc);
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener return newResourceResponse(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.
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener public ResourceResponse delete(DeleteRequest request) throws ResourceException {
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener if (request.getResourcePathObject().size() < 2) {
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener throw new NotFoundException("The object identifier did not include sufficient information to determine the object type and identifier of the object to update: " + request.getResourcePath());
0f8068490e3337fd4abce8b44b26c917bdc3bc8eJim Mitchener if (request.getRevision() == null || "".equals(request.getRevision())) {
0f8068490e3337fd4abce8b44b26c917bdc3bc8eJim Mitchener throw new ConflictException("Object passed into delete does not have revision it expects set.");
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener final String type = request.getResourcePathObject().parent().toString();
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener final String localId = request.getResourcePathObject().leaf();
39a2b1613dd6d8f45a32a9efda82fd0aead4cf43Brendan Mmiller int ver = DocumentUtil.parseVersion(request.getRevision()); // This throws ConflictException if parse fails
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff ODocument existingDoc = predefinedQueries.getByID(localId, type, db);
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener throw new NotFoundException("Object does not exist for delete on: " + request.getResourcePath());
812374fe11d8d95123cb0fd35447c43385a90549Chris Drake db.delete(existingDoc.getIdentity(), new OSimpleVersion(ver));
39a2b1613dd6d8f45a32a9efda82fd0aead4cf43Brendan Mmiller logger.debug("delete for id succeeded: {} revision: {}", localId, request.getRevision());
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."
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener public Promise<ResourceResponse, ResourceException> handlePatch(final Context context, final PatchRequest request) {
3a5174589eea431f6da6dc1bc3f20e3d358377f8Andi Egloff // TODO: impl
ed1b8fdb2169651136922938ef778b05dae17d5dBrendanMiller return adapt(new NotSupportedException("Patch not supported yet")).asPromise();
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener public Promise<ActionResponse, ResourceException> handleAction(final Context context, final ActionRequest request) {
79d12e9411155c9e79c08df9bb6412b2862e6064Brendan Mmiller Map<String, String> params = request.getAdditionalParameters();
79d12e9411155c9e79c08df9bb6412b2862e6064Brendan Mmiller switch (request.getActionAsEnum(Action.class)) {
79d12e9411155c9e79c08df9bb6412b2862e6064Brendan Mmiller String newPassword = params.get("password");
79d12e9411155c9e79c08df9bb6412b2862e6064Brendan Mmiller if (newUser == null || newPassword == null) {
ed1b8fdb2169651136922938ef778b05dae17d5dBrendanMiller return adapt(new BadRequestException("Expecting 'user' and 'password' parameters")).asPromise();
79d12e9411155c9e79c08df9bb6412b2862e6064Brendan Mmiller synchronized (dbLock) {
79d12e9411155c9e79c08df9bb6412b2862e6064Brendan Mmiller DBHelper.updateDbCredentials(dbURL, user, password, newUser, newPassword);
79d12e9411155c9e79c08df9bb6412b2862e6064Brendan Mmiller JsonValue config = connectionFactory.getConnection().read(context, Requests.newReadRequest("config", PID)).getContent();
79d12e9411155c9e79c08df9bb6412b2862e6064Brendan Mmiller UpdateRequest updateRequest = Requests.newUpdateRequest("config/" + PID, config);
79d12e9411155c9e79c08df9bb6412b2862e6064Brendan Mmiller connectionFactory.getConnection().update(context, updateRequest);
623d32cac07d4848161965256739521f868f54b5Brendan Miller return newActionResponse(new JsonValue(params)).asPromise();
623d32cac07d4848161965256739521f868f54b5Brendan Miller return newActionResponse(new JsonValue(command(request))).asPromise();
ed1b8fdb2169651136922938ef778b05dae17d5dBrendanMiller return adapt(new BadRequestException("Unknown action: " + request.getAction())).asPromise();
ed1b8fdb2169651136922938ef778b05dae17d5dBrendanMiller return adapt(new BadRequestException("Unknown action: " + request.getAction())).asPromise();
79d12e9411155c9e79c08df9bb6412b2862e6064Brendan Mmiller * Execute a database command according to the details in the action request.
79d12e9411155c9e79c08df9bb6412b2862e6064Brendan Mmiller * @param request the ActionRequest
79d12e9411155c9e79c08df9bb6412b2862e6064Brendan Mmiller * @return the number of affected rows/records.
280f5f217c81e0b90c2b526a8a03849c1371545cBrendan Mmiller * @throws ResourceException on failure to resolved query
79d12e9411155c9e79c08df9bb6412b2862e6064Brendan Mmiller public Object command(ActionRequest request) throws ResourceException {
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener return commands.query(request.getResourcePath(), request, db);
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.
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener public Promise<QueryResponse, ResourceException> handleQuery(final Context context, final QueryRequest request,
117941f80637bf0fe66cbb3a444430ef88f629a7Jim Mitchener // If paged results are requested then decode the cookie in order to determine
117941f80637bf0fe66cbb3a444430ef88f629a7Jim Mitchener // the index of the first result to be returned.
117941f80637bf0fe66cbb3a444430ef88f629a7Jim Mitchener final int requestPageSize = request.getPageSize();
117941f80637bf0fe66cbb3a444430ef88f629a7Jim Mitchener // Cookie containing offset of last request
117941f80637bf0fe66cbb3a444430ef88f629a7Jim Mitchener final String pagedResultsCookie = request.getPagedResultsCookie();
117941f80637bf0fe66cbb3a444430ef88f629a7Jim Mitchener final boolean pagedResultsRequested = requestPageSize > 0;
117941f80637bf0fe66cbb3a444430ef88f629a7Jim Mitchener // index of first record (used for SKIP/OFFSET)
117941f80637bf0fe66cbb3a444430ef88f629a7Jim Mitchener if (StringUtils.isNotEmpty(pagedResultsCookie)) {
117941f80637bf0fe66cbb3a444430ef88f629a7Jim Mitchener firstResultIndex = Integer.parseInt(pagedResultsCookie);
117941f80637bf0fe66cbb3a444430ef88f629a7Jim Mitchener } catch (final NumberFormatException e) {
ed1b8fdb2169651136922938ef778b05dae17d5dBrendanMiller return new BadRequestException("Invalid paged results cookie").asPromise();
117941f80637bf0fe66cbb3a444430ef88f629a7Jim Mitchener firstResultIndex = Math.max(0, request.getPagedResultsOffset());
117941f80637bf0fe66cbb3a444430ef88f629a7Jim Mitchener // Once cookie is processed Queries.query() can rely on the offset.
117941f80637bf0fe66cbb3a444430ef88f629a7Jim Mitchener request.setPagedResultsOffset(firstResultIndex);
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener List<ResourceResponse> results = query(request);
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener * Execute additional -count query if we are paging
a32c32954ffe171a0437ff1bc1287e9086f0bd26Chad Kienle // The number of results (if known)
7b16651ac38dde7780c1272067b1938e275325acJim Mitchener // count if requested
7b16651ac38dde7780c1272067b1938e275325acJim Mitchener switch (request.getTotalPagedResultsPolicy()) {
7b16651ac38dde7780c1272067b1938e275325acJim Mitchener // Get total if -count query is available
7b16651ac38dde7780c1272067b1938e275325acJim Mitchener final String countQueryId = request.getQueryId() + "-count";
7b16651ac38dde7780c1272067b1938e275325acJim Mitchener QueryRequest countRequest = Requests.copyOfQueryRequest(request);
7b16651ac38dde7780c1272067b1938e275325acJim Mitchener // Strip pagination parameters
7b16651ac38dde7780c1272067b1938e275325acJim Mitchener List<ResourceResponse> countResult = query(countRequest);
7b16651ac38dde7780c1272067b1938e275325acJim Mitchener if (countResult != null && !countResult.isEmpty()) {
7b16651ac38dde7780c1272067b1938e275325acJim Mitchener resultCount = countResult.get(0).getContent().get("total").asInteger();
d21f2a0e3f1ab2bb35ddb5f39936522c429fdd4cBrendan Miller logger.debug("Count query {} failed.", countQueryId);
d21f2a0e3f1ab2bb35ddb5f39936522c429fdd4cBrendan Miller logger.debug("No count query found with id {}", countQueryId);
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener final int remainingResults = resultCount - (firstResultIndex + results.size());
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener nextCookie = String.valueOf(firstResultIndex + requestPageSize);
623d32cac07d4848161965256739521f868f54b5Brendan Miller return newQueryResponse(nextCookie).asPromise();
7b16651ac38dde7780c1272067b1938e275325acJim Mitchener return newQueryResponse(nextCookie, EXACT, resultCount).asPromise();
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener public List<ResourceResponse> query(final QueryRequest request) throws ResourceException {
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener List<ResourceResponse> results = new ArrayList<ResourceResponse>();
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener logger.trace("Full id: {} Extracted type: {}", request.getResourcePath(), request.getResourcePath());
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);
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener List<ODocument> queryResult = queries.query(request.getResourcePath(), request, db);
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff Map<String, Object> convertedEntry = DocumentUtil.toMap(entry);
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff //docs.add(convertedEntry);
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener (String) convertedEntry.get(DocumentUtil.TAG_ID),
f242d1a2135048008020f65a531cd5a811170e4bJim Mitchener (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 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.
280f5f217c81e0b90c2b526a8a03849c1371545cBrendan Mmiller * @throws InternalServerErrorException
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff ODatabaseDocumentTx getConnection() throws InternalServerErrorException {
5c625c17767aae6167c744e9e1ad40c3d1455e31Andi Egloff int maxRetry = 100; // give it up to approx 10 seconds to recover
08b028f3cb5252a892956716ce12f4770e69f423Chad Kienle synchronized (dbLock) {
08b028f3cb5252a892956716ce12f4770e69f423Chad Kienle logger.info("Succeeded in acquiring connection from pool in retry attempt {}", retryCount);
49f22453ad3bcb0b3f0b50c405e3f5fb22ed0282Chris Drake } catch (com.orientechnologies.common.concur.lock.OLockException ex) {
08b028f3cb5252a892956716ce12f4770e69f423Chad Kienle // TODO: remove work-around once OrientDB resolves this condition
08b028f3cb5252a892956716ce12f4770e69f423Chad Kienle logger.warn("Failure reported acquiring connection from pool, retried {} times before giving up.", retryCount, ex);
08b028f3cb5252a892956716ce12f4770e69f423Chad Kienle "Failure reported acquiring connection from pool, retried " + retryCount + " times before giving up: "
08b028f3cb5252a892956716ce12f4770e69f423Chad Kienle logger.info("Pool acquire reported failure, retrying - attempt {}", retryCount);
08b028f3cb5252a892956716ce12f4770e69f423Chad Kienle logger.trace("Pool acquire failure detail ", ex);
08b028f3cb5252a892956716ce12f4770e69f423Chad Kienle Thread.sleep(100); // Give the DB time to complete what it's doing before retrying
08b028f3cb5252a892956716ce12f4770e69f423Chad Kienle // 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);
cbb34fee4a2f478defcf65ea232dd810b46c0f0bChris Drake * Detect if the root cause of the exception is a duplicate record.
cbb34fee4a2f478defcf65ea232dd810b46c0f0bChris Drake * This is necessary as the database may wrap this root cause in further exceptions,
cbb34fee4a2f478defcf65ea232dd810b46c0f0bChris Drake * masking the underlying cause
cbb34fee4a2f478defcf65ea232dd810b46c0f0bChris Drake * @param ex The throwable to check
cbb34fee4a2f478defcf65ea232dd810b46c0f0bChris Drake * @param maxLevels the maximum level of causes to check, avoiding the cost
cbb34fee4a2f478defcf65ea232dd810b46c0f0bChris Drake * of checking recursiveness
cbb34fee4a2f478defcf65ea232dd810b46c0f0bChris Drake private boolean isCauseRecordDuplicatedException(Throwable ex, int maxLevels) {
a1d206a2a22b5cde9b00633ea4472ae0b144d695Brendan Mmiller return isCauseException (ex, ORecordDuplicatedException.class, maxLevels);
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
399a0a33fc502af77b2e74bb087bfa51b81422c0Brendan Mmiller 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.
399a0a33fc502af77b2e74bb087bfa51b81422c0Brendan Mmiller static OrientDBRepoService getRepoBootService(Map<String, Object> repoConfig) {
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: "
10fed32259a7430282ad1db9194450808ad5d932Jim Mitchener * Initialize the instance 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
08b028f3cb5252a892956716ce12f4770e69f423Chad Kienle synchronized (dbLock) {
10fed32259a7430282ad1db9194450808ad5d932Jim Mitchener user = config.get(CONFIG_USER).defaultTo("admin").asString();
10fed32259a7430282ad1db9194450808ad5d932Jim Mitchener password = config.get(CONFIG_PASSWORD).defaultTo("admin").asString();
10fed32259a7430282ad1db9194450808ad5d932Jim Mitchener poolMinSize = config.get(CONFIG_POOL_MIN_SIZE).defaultTo(DEFAULT_POOL_MIN_SIZE).asInteger();
10fed32259a7430282ad1db9194450808ad5d932Jim Mitchener poolMaxSize = config.get(CONFIG_POOL_MAX_SIZE).defaultTo(DEFAULT_POOL_MAX_SIZE).asInteger();
79d12e9411155c9e79c08df9bb6412b2862e6064Brendan Mmiller Map<String, String> queryMap = config.get(CONFIG_QUERIES)
79d12e9411155c9e79c08df9bb6412b2862e6064Brendan Mmiller Map<String, String> commandMap = config.get(CONFIG_COMMANDS)
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();
dcb5656be9fc7c325b148ad479fb959c0eadc414Chris Drake return config.get(CONFIG_USER).defaultTo("admin").asString();
dcb5656be9fc7c325b148ad479fb959c0eadc414Chris Drake return config.get(CONFIG_PASSWORD).defaultTo("admin").asString();
dcb5656be9fc7c325b148ad479fb959c0eadc414Chris Drake return config.get(CONFIG_POOL_MIN_SIZE).defaultTo(DEFAULT_POOL_MIN_SIZE).asInteger();
dcb5656be9fc7c325b148ad479fb959c0eadc414Chris Drake return config.get(CONFIG_POOL_MAX_SIZE).defaultTo(DEFAULT_POOL_MAX_SIZE).asInteger();
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);
dcb5656be9fc7c325b148ad479fb959c0eadc414Chris Drake && !existingConfig.get("embeddedServer").equals(newConfig.get("embeddedServer"))) {
dcb5656be9fc7c325b148ad479fb959c0eadc414Chris Drake // The embedded server configuration has changed so re-initialize it.
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