Package org.openengsb.core.ekb.graph.orient.internal

Source Code of org.openengsb.core.ekb.graph.orient.internal.OrientModelGraph

/**
* Licensed to the Austrian Association for Software Tool Integration (AASTI)
* under one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information regarding copyright
* ownership. The AASTI licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.openengsb.core.ekb.graph.orient.internal;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.openengsb.core.api.model.ModelDescription;
import org.openengsb.core.ekb.api.ModelGraph;
import org.openengsb.core.ekb.api.transformation.TransformationDescription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.orientechnologies.orient.core.Orient;
import com.orientechnologies.orient.core.db.graph.OGraphDatabase;
import com.orientechnologies.orient.core.index.OIndexes;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.sql.query.OSQLSynchQuery;

/**
* The EKB model graph is an implementation of the model graph. It uses a graph database as storage.
*
* It usually get filled by two components: the TransformationEngineService and the ModelRegistryService. The
* TransformationEngineService inserts all transformations it get saved as new edges into the graph database. The
* ModelRegistry notifies the graph whenever new models get available or models get unavailable.
*/
public final class OrientModelGraph implements ModelGraph {
    private static final Logger LOGGER = LoggerFactory.getLogger(OrientModelGraph.class);
    private OGraphDatabase graph;
    private Map<String, TransformationDescription> descriptions;
    private AtomicLong counter;
    private ReadWriteLock lockingMechanism;

    public OrientModelGraph() {
        startup();
        descriptions = new HashMap<String, TransformationDescription>();
        counter = new AtomicLong(0L);
        lockingMechanism = new ReentrantReadWriteLock(true);
    }

    private void startup() {
        ClassLoader origClassLoader = Thread.currentThread().getContextClassLoader();
        try {
            ClassLoader orientClassLoader = OIndexes.class.getClassLoader();
            Thread.currentThread().setContextClassLoader(orientClassLoader);
            graph = new OGraphDatabase("memory:ekbgraphdb").create();
        } finally {
            Thread.currentThread().setContextClassLoader(origClassLoader);
        }
        Orient.instance().removeShutdownHook();
        graph.createVertexType("Models");
    }

    /**
     * Shuts down the graph database
     */
    public void shutdown() {
        ClassLoader origClassLoader = Thread.currentThread().getContextClassLoader();
        try {
            ClassLoader orientClassLoader = OIndexes.class.getClassLoader();
            Thread.currentThread().setContextClassLoader(orientClassLoader);
            graph.close();
        } finally {
            Thread.currentThread().setContextClassLoader(origClassLoader);
        }
    }

    /**
     * Deletes all edges and nodes from the graph database. This method is used in the tests to have at every test the
     * same start situation.
     */
    public void cleanDatabase() {
        lockingMechanism.writeLock().lock();
        try {
            for (ODocument edge : graph.browseEdges()) {
                edge.delete();
            }
            for (ODocument node : graph.browseVertices()) {
                node.delete();
            }
        } finally {
            lockingMechanism.writeLock().unlock();
        }
    }

    @Override
    public void addModel(ModelDescription model) {
        lockingMechanism.writeLock().lock();
        try {
            ODocument node = getModel(model.toString());
            if (node == null) {
                node = graph.createVertex("Models");
                OrientModelGraphUtils.setIdFieldValue(node, model.toString());
            }
            OrientModelGraphUtils.setActiveFieldValue(node, true);
            node.save();
            LOGGER.debug("Added model {} to the graph database", model);
        } finally {
            lockingMechanism.writeLock().unlock();
        }
    }

    @Override
    public void removeModel(ModelDescription model) {
        lockingMechanism.writeLock().lock();
        try {
            ODocument node = getModel(model.toString());
            if (node == null) {
                LOGGER.warn("Couldn't remove model {} since it wasn't present in the graph database", model);
                return;
            }
            OrientModelGraphUtils.setActiveFieldValue(node, false);
            node.save();
            LOGGER.debug("Removed model {} from the graph database", model);
        } finally {
            lockingMechanism.writeLock().unlock();
        }
    }

    @Override
    public void addTransformation(TransformationDescription description) {
        lockingMechanism.writeLock().lock();
        try {
            checkTransformationDescriptionId(description);
            ODocument source = getOrCreateModel(description.getSourceModel().toString());
            ODocument target = getOrCreateModel(description.getTargetModel().toString());
            ODocument edge = graph.createEdge(source, target);
            OrientModelGraphUtils.setIdFieldValue(edge, description.getId());
            if (description.getFileName() != null) {
                OrientModelGraphUtils.setFilenameFieldValue(edge, description.getFileName());
            }
            OrientModelGraphUtils.fillEdgeWithPropertyConnections(edge, description);
            edge.save();
            descriptions.put(description.getId(), description);
            LOGGER.debug("Added transformation description {} to the graph database", description);
        } finally {
            lockingMechanism.writeLock().unlock();
        }
    }

    @Override
    public void removeTransformation(TransformationDescription description) {
        lockingMechanism.writeLock().lock();
        try {
            String source = description.getSourceModel().toString();
            String target = description.getTargetModel().toString();
            for (ODocument edge : getEdgesBetweenModels(source, target)) {
                String id = OrientModelGraphUtils.getIdFieldValue(edge);
                if (description.getId() == null && isInternalId(id)) {
                    edge.delete();
                    descriptions.remove(id);
                    LOGGER.debug("Removed transformation description {} from the graph database", id);
                } else if (id.equals(description.getId())) {
                    edge.delete();
                    descriptions.remove(id);
                    LOGGER.debug("Removed transformation description {} from the graph database", id);
                    break;
                }
            }
        } finally {
            lockingMechanism.writeLock().unlock();
        }
    }

    @Override
    public List<TransformationDescription> getTransformationsPerFileName(String filename) {
        lockingMechanism.readLock().lock();
        try {
            String query = String.format("select from E where %s = ?", OrientModelGraphUtils.FILENAME);
            List<ODocument> edges = graph.query(new OSQLSynchQuery<ODocument>(query), filename);
            List<TransformationDescription> result = new ArrayList<TransformationDescription>();
            for (ODocument edge : edges) {
                result.add(descriptions.get(OrientModelGraphUtils.getIdFieldValue(edge)));
            }
            return result;
        } finally {
            lockingMechanism.readLock().unlock();
        }
    }

    @Override
    public List<TransformationDescription> getTransformationPath(ModelDescription source, ModelDescription target,
            List<String> ids) {
        lockingMechanism.readLock().lock();
        try {
            if (ids == null) {
                ids = new ArrayList<String>();
            }
            List<ODocument> path = recursivePathSearch(source.toString(), target.toString(), ids, new ODocument[0]);
            List<TransformationDescription> result = new ArrayList<TransformationDescription>();
            if (path != null) {
                for (ODocument edge : path) {
                    result.add(descriptions.get(OrientModelGraphUtils.getIdFieldValue(edge)));
                }
                return result;
            }
            throw new IllegalArgumentException("No transformation description found");
        } finally {
            lockingMechanism.readLock().unlock();
        }
    }

    @Override
    public Boolean isTransformationPossible(ModelDescription source, ModelDescription target, List<String> ids) {
        lockingMechanism.readLock().lock();
        try {
            getTransformationPath(source, target, ids);
            return true;
        } catch (IllegalArgumentException e) {
            return false;
        } finally {
            lockingMechanism.readLock().unlock();
        }
    }

    /**
     * Tests if a transformation description has an id and adds an unique id if it hasn't one.
     */
    private void checkTransformationDescriptionId(TransformationDescription description) {
        if (description.getId() == null) {
            description.setId("EKBInternal-" + counter.incrementAndGet());
        }
    }

    /**
     * Returns true if the given id is an automatically generated id, or a user defined one.
     */
    private boolean isInternalId(String id) {
        return id.startsWith("EKBInternal-");
    }

    /**
     * Returns all edges which start at the source model and end in the target model.
     */
    private List<ODocument> getEdgesBetweenModels(String source, String target) {
        ODocument from = getModel(source);
        ODocument to = getModel(target);
        String query = "select from E where out = ? AND in = ?";
        return graph.query(new OSQLSynchQuery<ODocument>(query), from, to);
    }

    /**
     * Returns all neighbors of a model.
     */
    private List<ODocument> getNeighborsOfModel(String model) {
        String query = String.format("select from Models where in.out.%s in [?]", OGraphDatabase.LABEL);
        List<ODocument> neighbors = graph.query(new OSQLSynchQuery<ODocument>(query), model);
        return neighbors;
    }

    /**
     * Returns the model with the given name, or creates one if it isn't existing until then and returns the new one.
     */
    private ODocument getOrCreateModel(String model) {
        ODocument node = getModel(model);
        if (node == null) {
            node = graph.createVertex("Models");
            OrientModelGraphUtils.setIdFieldValue(node, model.toString());
            OrientModelGraphUtils.setActiveFieldValue(node, false);
            node.save();
        }
        return node;
    }

    /**
     * Returns the model with the given name.
     */
    private ODocument getModel(String model) {
        String query = String.format("select from Models where %s = ?", OGraphDatabase.LABEL);
        List<ODocument> from = graph.query(new OSQLSynchQuery<ODocument>(query), model);
        if (from.size() > 0) {
            return from.get(0);
        } else {
            return null;
        }
    }

    /**
     * Recursive path search function. It performs a depth first search with integrated loop check to find a path from
     * the start model to the end model. If the id list is not empty, then the function only returns a path as valid if
     * all transformations defined with the id list are in the path. It also takes care of models which aren't
     * available. Returns null if there is no path found.
     */
    private List<ODocument> recursivePathSearch(String start, String end, List<String> ids, ODocument... steps) {
        List<ODocument> neighbors = getNeighborsOfModel(start);
        for (ODocument neighbor : neighbors) {
            if (alreadyVisited(neighbor, steps) || !OrientModelGraphUtils.getActiveFieldValue(neighbor)) {
                continue;
            }
            ODocument nextStep = getEdgeWithPossibleId(start, OrientModelGraphUtils.getIdFieldValue(neighbor), ids);
            if (nextStep == null) {
                continue;
            }
            if (OrientModelGraphUtils.getIdFieldValue(neighbor).equals(end)) {
                List<ODocument> result = new ArrayList<ODocument>();
                List<String> copyIds = new ArrayList<String>(ids);
                for (ODocument step : steps) {
                    String id = OrientModelGraphUtils.getIdFieldValue(step);
                    if (id != null && copyIds.contains(id)) {
                        copyIds.remove(id);
                    }
                    result.add(step);
                }
                String id = OrientModelGraphUtils.getIdFieldValue(nextStep);
                if (id != null && copyIds.contains(id)) {
                    copyIds.remove(id);
                }
                result.add(nextStep);
                if (copyIds.isEmpty()) {
                    return result;
                }
            }
            ODocument[] path = Arrays.copyOf(steps, steps.length + 1);
            path[path.length - 1] = nextStep;
            List<ODocument> check =
                recursivePathSearch(OrientModelGraphUtils.getIdFieldValue(neighbor), end, ids, path);
            if (check != null) {
                return check;
            }
        }
        return null;
    }

    /**
     * Returns an edge between the start and the end model. If there is an edge which has an id which is contained in
     * the given id list, then this transformation is returned. If not, then the first found is returned.
     */
    private ODocument getEdgeWithPossibleId(String start, String end, List<String> ids) {
        List<ODocument> edges = getEdgesBetweenModels(start, end);
        for (ODocument edge : edges) {
            if (ids.contains(OrientModelGraphUtils.getIdFieldValue(edge))) {
                return edge;
            }
        }
        return edges.size() != 0 ? edges.get(0) : null;
    }

    /**
     * Checks if a model is already visited in the path search algorithm. Needed for the loop detection.
     */
    private boolean alreadyVisited(ODocument neighbor, ODocument[] steps) {
        for (ODocument step : steps) {
            ODocument out = graph.getOutVertex(step);
            if (out.equals(neighbor)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns true if the model is currently active, returns false if not.
     */
    public boolean isModelActive(ModelDescription model) {
        lockingMechanism.readLock().lock();
        try {
            ODocument node = getModel(model.toString());
            return OrientModelGraphUtils.getActiveFieldValue(node);
        } finally {
            lockingMechanism.readLock().unlock();
        }
    }
}
TOP

Related Classes of org.openengsb.core.ekb.graph.orient.internal.OrientModelGraph

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.