com.wingnest.play2.origami.plugin.OrigamiPlugin.java Source code

Java tutorial

Introduction

Here is the source code for com.wingnest.play2.origami.plugin.OrigamiPlugin.java

Source

/*
 * Copyright since 2013 Shigeru GOUGI (sgougi@gmail.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.wingnest.play2.origami.plugin;

import static com.wingnest.play2.origami.plugin.ConfigConsts.*;
import static com.wingnest.play2.origami.plugin.OrigamiLogger.debug;
import static com.wingnest.play2.origami.plugin.OrigamiLogger.error;
import static com.wingnest.play2.origami.plugin.OrigamiLogger.info;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.text.SimpleDateFormat;
import java.util.*;

import javax.persistence.Id;
import javax.persistence.Transient;
import javax.persistence.Version;

import org.apache.commons.collections.CollectionUtils;

import play.Application;
import play.Configuration;
import play.Play;
import play.Plugin;

import com.orientechnologies.orient.client.remote.OEngineRemote;
import com.orientechnologies.orient.core.Orient;
import com.orientechnologies.orient.core.config.OGlobalConfiguration;
import com.orientechnologies.orient.core.db.ODatabaseListener;
import com.orientechnologies.orient.core.db.graph.OGraphDatabase;
import com.orientechnologies.orient.core.hook.ORecordHook;
import com.orientechnologies.orient.core.index.OIndex;
import com.orientechnologies.orient.core.metadata.schema.OClass;
import com.orientechnologies.orient.core.metadata.schema.OProperty;
import com.orientechnologies.orient.core.metadata.schema.OSchema;
import com.orientechnologies.orient.core.metadata.schema.OType;
import com.orientechnologies.orient.server.OServer;
import com.orientechnologies.orient.server.OServerMain;
import com.wingnest.play2.origami.GraphDB;
import com.wingnest.play2.origami.GraphEdgeModel;
import com.wingnest.play2.origami.GraphVertexModel;
import com.wingnest.play2.origami.annotations.CompositeIndex;
import com.wingnest.play2.origami.annotations.DisupdateFlag;
import com.wingnest.play2.origami.annotations.Index;
import com.wingnest.play2.origami.plugin.exceptions.OrigamiUnexpectedException;
import com.wingnest.play2.origami.plugin.utils.ZipCompressionUtils;

final public class OrigamiPlugin extends Plugin {

    public static final Map<String, Class<?>> graphEntityMap = new HashMap<String, Class<?>>();
    private static final String ORIENTDB_WWW_PATH = "orientdb.www.path";

    public static String url;
    public static String user;
    public static String passwd;
    public static List<String> models;

    final private Application application;
    private static OServer server;

    // /

    public OrigamiPlugin(final Application application) {
        this.application = application;
        Runtime.getRuntime().addShutdownHook(new Thread() {
            public void run() {
                serverShutdown();
            }
        });
    }

    @Override
    public void onStart() {
        if (server == null) {
            if (Orient.getHomePath() == null) {
                System.setProperty(Orient.ORIENTDB_HOME, ".");
            }
            OGlobalConfiguration.CACHE_LEVEL1_ENABLED.setValue(false);
            configure();
            serverStart();
        }
        registerGraphClasses();
    }

    // ///////////////////////////////////////////////////////////////////////////////////////////////

    synchronized private static void serverStart() {
        if (url.startsWith("remote:")) {
            Orient.instance().registerEngine(new OEngineRemote());
        } else {
            if (url.startsWith("local:")) {
                final File db = new File(url.substring("local:".length()));
                final Configuration c = Play.application().configuration();
                if (c.getBoolean(CONF_ENABLE_DB_BACKUP_AT_STARTUP) && db.exists()) {
                    OrigamiLogger.info("create database backup");
                    try {
                        final SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-z");
                        ZipCompressionUtils.compress(db,
                                new File(url.substring("local:".length()) + "-" + f.format(new Date()) + ".zip"));
                    } catch (Exception e) {
                        throw new IllegalStateException(e);
                    }
                }
            }
            runEmbeddedOrientDB();
        }
    }

    synchronized private static void serverShutdown() {
        GraphDB.initializeAttributes();
        if (server != null) {
            OrigamiLogger.debug("call server.shutdown()");
            server.shutdown();
            server = null;
        }
        final Orient inst = Orient.instance();
        if (inst != null) {
            OrigamiLogger.debug("call Orient.instance().shutdown()");
            inst.shutdown();
        }
    }

    private static void configure() {
        final Configuration c = Play.application().configuration();
        url = c.getString(CONF_ORIENT_DB_URL, "memory:temp");
        user = c.getString(CONF_ORIENT_DB_USER, "admin");
        passwd = c.getString(CONF_ORIENT_DB_PASSWORD, "admin");
        models = c.getStringList(CONF_ORIENT_DB_MODELS, new ArrayList<String>() {
            {
                add("models");
            }
        });
    }

    private static void runEmbeddedOrientDB() {
        FileInputStream fis = null;
        try {
            /* orient server */ {
                final String cfile = Play.application().configuration().getString(CONF_ORIENT_DB_CONFIG_FILE);
                final InputStream is;
                if (cfile != null) {
                    final File aFile = new File(cfile);
                    OrigamiLogger.info("db.config in application was used : " + aFile.getAbsolutePath());
                    fis = new FileInputStream(new File(cfile));
                    is = fis;
                } else {
                    OrigamiLogger.info("default db.config in Origami Plugin was used");
                    is = OrigamiPlugin.class.getClassLoader().getResourceAsStream("db.config");
                }
                server = OServerMain.create();
                server.startup(is);
                info("OrientDB of embedded mode was started");
            }
            /* web server */ {
                final String orientDBWwwPath = Play.application().configuration()
                        .getString(CONF_ORIENT_DB_WWW_PATH);
                if (System.getProperty(ORIENTDB_WWW_PATH) == null && orientDBWwwPath != null) {
                    final File wwwPath = new File(orientDBWwwPath);
                    if (!wwwPath.exists() || !wwwPath.isDirectory()) {
                        final String mes = String.format("www directory not found : %s", wwwPath.getAbsolutePath());
                        OrigamiLogger.error(mes);
                        throw new IllegalStateException(mes);
                    }
                    System.setProperty(ORIENTDB_WWW_PATH, wwwPath.getCanonicalPath());
                }
                if (System.getProperty(ORIENTDB_WWW_PATH) != null) {
                    server.activate();
                    OrigamiLogger.info("WWW Server was just activated : %s", System.getProperty(ORIENTDB_WWW_PATH));
                } else {
                    OrigamiLogger.info(
                            "WWW Server is not activated : application.conf's %s property was not provided",
                            CONF_ORIENT_DB_WWW_PATH);
                }
            }
        } catch (Exception e) {
            throw new OrigamiUnexpectedException(e);
        } finally {
            if (fis != null)
                try {
                    fis.close();
                } catch (Exception dummy) {
                }
        }
    }

    // ///

    private void registerGraphClasses() {
        final OGraphDatabase db = GraphDB.open();
        try {
            debug("Registering Graph Classes");

            final Set<Class<GraphVertexModel>> vertexClasses = new HashSet<Class<GraphVertexModel>>();
            final Set<Class<GraphEdgeModel>> edgeClasses = new HashSet<Class<GraphEdgeModel>>();

            for (String pkg : models) {
                vertexClasses.addAll(getSubTypesOf(pkg, GraphVertexModel.class));
                edgeClasses.addAll(getSubTypesOf(pkg, GraphEdgeModel.class));
            }

            @SuppressWarnings("unchecked")
            final Collection<Class<?>> javaClasses = CollectionUtils.union(vertexClasses, edgeClasses);

            final Class<?>[] javaClassArray = javaClasses.toArray(new Class<?>[0]);
            Arrays.sort(javaClassArray, new Comparator<Class<?>>() {
                @Override
                public int compare(Class<?> o1, Class<?> o2) {
                    if (o1.equals(o2))
                        return 0;
                    if (o1.isAssignableFrom(o2))
                        return -1;
                    if (o2.isAssignableFrom(o1))
                        return 1;
                    int o1cnt = calSuperclassCount(o1);
                    int o2cnt = calSuperclassCount(o2);
                    return (o1cnt - o2cnt);
                }
            });

            javaClasses.clear();
            javaClasses.addAll(Arrays.asList(javaClassArray));

            final OSchema schema = db.getMetadata().getSchema();
            for (final Class<?> javaClass : javaClasses) {
                final String entityName = javaClass.getSimpleName();
                final OClass oClass;
                if (GraphVertexModel.class.isAssignableFrom(javaClass)) {
                    final String className = javaClass.getSimpleName();
                    debug("Entity: %s", className);
                    if (schema.existsClass(className)) {
                        oClass = schema.getClass(className);
                    } else {
                        oClass = db.createVertexType(className);
                    }
                    graphEntityMap.put(className, javaClass);
                    final Class<?> sclass = javaClass.getSuperclass();
                    if (javaClasses.contains(sclass)) {
                        final OClass sClass = db.getMetadata().getSchema().getClass(sclass.getSimpleName());
                        db.getMetadata().getSchema().getClass(entityName).setSuperClass(sClass);
                    }
                } else if (GraphEdgeModel.class.isAssignableFrom(javaClass)) {
                    final String className = javaClass.getSimpleName();
                    debug("Entity: %s", className);
                    if (schema.existsClass(className)) {
                        oClass = schema.getClass(className);
                    } else {
                        oClass = db.createEdgeType(className);
                    }
                    graphEntityMap.put(className, javaClass);
                    final Class<?> sclass = javaClass.getSuperclass();
                    if (javaClasses.contains(sclass)) {
                        final OClass sClass = db.getMetadata().getSchema().getClass(sclass.getSimpleName());
                        db.getMetadata().getSchema().getClass(entityName).setSuperClass(sClass);
                    }
                } else {
                    throw new IllegalStateException("bug!?");
                }
                maintainProperties(oClass, javaClass);
            }
            debug("Registering Database Listeners");
            for (final Class<? extends ODatabaseListener> listener : getSubTypesOf("listeners",
                    ODatabaseListener.class)) {
                debug("Listener: %s", listener.getName());
                GraphDB.getListeners().add(listener);
            }
            debug("Registering Record Hooks");
            for (final Class<? extends ORecordHook> hook : getSubTypesOf("hooks", ORecordHook.class)) {
                debug("Hook: %s", hook.getName());
                GraphDB.getGraphHooks().add(hook);
            }
        } catch (Exception e) {
            throw new OrigamiUnexpectedException(e);
        } finally {
            db.close();
        }
    }

    @SuppressWarnings("unchecked")
    private <T> Set<Class<T>> getSubTypesOf(final String packageName, final Class<T> clazz) {
        final Set<Class<T>> classes = new HashSet<Class<T>>();
        try {
            final Set<String> classNames = new HashSet<String>();
            classNames.addAll(play.libs.Classpath.getTypes(application, packageName));
            for (final String clazzName : classNames) {
                final Class<?> c = Class.forName(clazzName, true, application.classloader());
                if (clazz.isAssignableFrom(c))
                    classes.add((Class<T>) c);
            }
        } catch (Exception e) {
            throw new OrigamiUnexpectedException(e);
        }
        return classes;
    }

    @SuppressWarnings("unchecked")
    private void maintainProperties(final OClass oClass, final Class<?> javaClass) {
        final Map<String, Map<String, Object>> compositeIndexMap = new HashMap<String, Map<String, Object>>();
        final Map<String, OIndex<?>> classIndexCache = new HashMap<String, OIndex<?>>();
        final Map<String, OIndex<?>> compositeIndexCache = new HashMap<String, OIndex<?>>();
        final Set<String> wkCurIndexNames = new HashSet<String>();
        //      for ( final OProperty prop : oClass.properties() ) {
        //         debug("[b] prop name =%s, type = %s", prop.getName(), prop.getType());
        //      }
        for (final OIndex<?> index : oClass.getClassIndexes()) {
            wkCurIndexNames.add(index.getName());
            if (index.getName().indexOf('.') > -1) {
                classIndexCache.put(index.getName(), index);
            } else {
                compositeIndexCache.put(index.getName(), index);
            }
            //         debug("[b] index name =%s, type = %s", index.getName(), index.getType());
        }
        for (final Field field : javaClass.getDeclaredFields()) {
            if (Modifier.isStatic(field.getModifiers()) || field.isAnnotationPresent(Id.class)
                    || field.isAnnotationPresent(Version.class) || field.isAnnotationPresent(Transient.class)
                    || field.isAnnotationPresent(DisupdateFlag.class))
                continue;

            OProperty prop = oClass.getProperty(field.getName());
            final OType type = guessType(field);
            if (prop == null) {
                if (type != null) {
                    debug("create property : %s", field.getName());
                    prop = oClass.createProperty(field.getName(), type);
                }
            } else {
                if (!type.equals(prop.getType())) {
                    deleteIndex(oClass, oClass.getName() + "." + field.getName(), classIndexCache,
                            compositeIndexCache);
                    debug("drop property : %s", field.getName());
                    oClass.dropProperty(field.getName());
                    debug("create property : %s", field.getName());
                    prop = oClass.createProperty(field.getName(), type);
                }
            }
            final Index index = field.getAnnotation(Index.class);
            if (index != null) {
                final String indexName = makeIndexName(javaClass, field);
                OIndex<?> oindex = classIndexCache.get(indexName);
                if (oindex == null) {
                    debug("create Class Index : %s.%s", javaClass.getSimpleName(), field.getName());
                    if (prop != null) {
                        oindex = oClass.createIndex(indexName, index.indexType(), field.getName());
                    } else {
                        error("could not create Class Index : property(%s.%s) has't type", javaClass.getName(),
                                field.getName());
                    }
                }
                if (oindex != null) {
                    wkCurIndexNames.remove(oindex.getName());
                }
            }
            final CompositeIndex cindex = field.getAnnotation(CompositeIndex.class);
            if (cindex != null) {
                final String indexName = javaClass.getSimpleName() + "_" + cindex.indexName();
                Map<String, Object> ci = compositeIndexMap.get(indexName);
                if (ci == null) {
                    ci = new HashMap<String, Object>();
                    ci.put("fields", new HashSet<Field>());
                    ci.put("indexType", OClass.INDEX_TYPE.UNIQUE);
                }
                if (!cindex.indexType().equals(OClass.INDEX_TYPE.UNIQUE))
                    ci.put("indexType", cindex.indexType());
                ((Set<Field>) ci.get("fields")).add(field);
                compositeIndexMap.put(indexName, ci);
            }
        }

        for (final String cindexName : compositeIndexMap.keySet()) {
            final Map<String, Object> ci = compositeIndexMap.get(cindexName);
            final Set<Field> fields = (Set<Field>) ci.get("fields");
            final String[] fieldNames = new String[fields.size()];
            int i = 0;
            for (final Field f : fields) {
                fieldNames[i++] = f.getName();
            }
            final OIndex<?> oindex = compositeIndexCache.get(cindexName);
            if (oindex != null && !CollectionUtils.isEqualCollection(Arrays.asList(fieldNames),
                    oindex.getDefinition().getFields())) {
                debug("recreate composite index : %s", cindexName);
                deleteIndex(oClass, cindexName, classIndexCache, compositeIndexCache);
            } else if (oindex == null) {
                debug("create composite index : %s", cindexName);
            }
            oClass.createIndex(cindexName, (OClass.INDEX_TYPE) ci.get("indexType"), fieldNames);
            wkCurIndexNames.remove(cindexName);
        }

        for (final String indexName : wkCurIndexNames) {
            final int ind = indexName.indexOf('.');
            if (ind > -1) {
                debug("delete index : %s", indexName);
            } else {
                debug("delete composite index : %s", indexName);
            }
            deleteIndex(oClass, indexName, classIndexCache, compositeIndexCache);
        }

        //      for ( final OProperty prop : oClass.properties() ) {
        //         debug("[a] prop name =%s, type = %s", prop.getName(), prop.getType());
        //      }
        //      for ( final OIndex<?> index : oClass.getClassIndexes() ) {
        //         debug("[a] class index name =%s, type = %s", index.getName(), index.getType());
        //      }
    }

    private void deleteIndex(final OClass oClass, final String indexName,
            final Map<String, OIndex<?>> classIndexCache, final Map<String, OIndex<?>> compositeIndexCache) {
        final OIndex<?> oindex;
        if (indexName.indexOf('.') > -1) {
            oindex = classIndexCache.get(indexName);
        } else {
            oindex = compositeIndexCache.get(indexName);
        }
        if (oindex != null) {
            debug("drop index %s", oindex.getName());
            GraphDB.open().getMetadata().getIndexManager().dropIndex(indexName).flush();
        }
    }

    private OType guessType(final Field field) {
        if (String.class.isAssignableFrom(field.getType()))
            return OType.STRING;
        if (Date.class.isAssignableFrom(field.getType()))
            return OType.DATETIME;
        if (Integer.class.isAssignableFrom(field.getType()) || int.class.isAssignableFrom(field.getType()))
            return OType.INTEGER;
        if (Long.class.isAssignableFrom(field.getType()) || long.class.isAssignableFrom(field.getType()))
            return OType.LONG;
        if (Float.class.isAssignableFrom(field.getType()) || float.class.isAssignableFrom(field.getType()))
            return OType.FLOAT;
        if (Double.class.isAssignableFrom(field.getType()) || double.class.isAssignableFrom(field.getType()))
            return OType.DOUBLE;
        if (Boolean.class.isAssignableFrom(field.getType()) || boolean.class.isAssignableFrom(field.getType()))
            return OType.BOOLEAN;
        if (Byte.class.isAssignableFrom(field.getType()))
            return OType.BYTE;
        if (Short.class.isAssignableFrom(field.getType()) || short.class.isAssignableFrom(field.getType()))
            return OType.SHORT;
        return null;
    }

    private String makeIndexName(final Class<?> javaClass, final Field field) {
        return new StringBuffer().append(javaClass.getSimpleName()).append(".").append(field.getName()).toString();
    }

    private int calSuperclassCount(final Class<?> clazz) {
        int cnt = 0;
        Class<?> sc = clazz.getSuperclass();
        while (sc != null) {
            cnt++;
            sc = sc.getSuperclass();
        }
        return cnt;
    }

}