com.quinsoft.zeidon.standardoe.TaskImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.quinsoft.zeidon.standardoe.TaskImpl.java

Source

/**
This file is part of the Zeidon Java Object Engine (Zeidon JOE).
    
Zeidon JOE is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
    
Zeidon JOE is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.
    
You should have received a copy of the GNU Lesser General Public License
along with Zeidon JOE.  If not, see <http://www.gnu.org/licenses/>.
    
Copyright 2009-2015 QuinSoft
 */
package com.quinsoft.zeidon.standardoe;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;

import org.apache.commons.lang3.StringUtils;

import com.google.common.collect.MapMaker;
import com.quinsoft.zeidon.Application;
import com.quinsoft.zeidon.CommitOptions;
import com.quinsoft.zeidon.DeserializeOi;
import com.quinsoft.zeidon.DropTaskCleanup;
import com.quinsoft.zeidon.EntityCache;
import com.quinsoft.zeidon.ObjectEngine;
import com.quinsoft.zeidon.SerializeOi;
import com.quinsoft.zeidon.Task;
import com.quinsoft.zeidon.TaskLogger;
import com.quinsoft.zeidon.View;
import com.quinsoft.zeidon.ZeidonException;
import com.quinsoft.zeidon.ZeidonLogger;
import com.quinsoft.zeidon.objectdefinition.EntityDef;

/**
 * Comparable interface is implemented so that we can easily sort tasks by ID.
 *
 * @author DG
 *
 */
class TaskImpl extends AbstractTaskQualification implements Task, Comparable<TaskImpl> {
    private final TaskLogger logger;
    private final ZeidonLogger dblogger;
    private final ViewNameList viewNameList;
    private final ConcurrentMap<ViewImpl, Boolean> viewList;
    private final String taskId;
    private final JavaObjectEngine objectEngine;
    private boolean isValid;
    private final boolean isSystemTask;

    private String tempDir;
    private String userId;
    private String description;

    /**
     * New versions of entities (created by the createTemperal... methods) are given
     * a unique version number so the OE can verify if all linked instances have been
     * versioned.
     **/
    private final AtomicLong versionCounter = new AtomicLong();
    private ScalaHelper scalaHelper;

    /**
     * Keep track of entity caches for this task.
     */
    private Map<String, EntityCache> entityCacheMap;

    private Map<Application, Map<String, Map<String, String>>> configOverrideMap;

    private List<DropTaskCleanup> cleanupWork;

    TaskImpl(JavaObjectEngine objectEngine, Application app, String taskId) {
        super(app);
        isValid = true;
        this.taskId = taskId;
        this.objectEngine = objectEngine;

        // Check to see if this is the system task.  It's ok to use '==' instead of .equals()
        // because we control the creation of the system task.
        isSystemTask = (taskId == ObjectEngine.ZEIDON_SYSTEM_APP_NAME);

        // viewList is a weak map.  This way we can get a list of views if necessary
        // (the browser needs this) but it won't hold onto views once they are no longer
        // in use.
        viewList = new MapMaker().concurrencyLevel(2).weakKeys().makeMap();
        viewNameList = new ViewNameList();
        description = this.taskId;

        String prefix = String.format(" [%4s] ", taskId);
        logger = new TaskLogger(prefix);
        dblogger = new TaskLogger(prefix);
        String logLevel = this.readZeidonConfig(app.getName(), "InitialLogLevel");
        if (!StringUtils.isBlank(logLevel)) {
            logger.setLevel(logLevel);
            dblogger.setLevel(logLevel);
        }

        log().info("Created new task for app %s, task ID = %s", app.getName(), taskId);
    }

    /**
     * This creates a boot-strap task used only for logging.
     */
    TaskImpl(JavaObjectEngine objectEngine) {
        super(null);
        taskId = "null";
        this.objectEngine = objectEngine;
        viewList = null;
        viewNameList = null;
        logger = new TaskLogger(" [bootstrap] ");
        dblogger = new TaskLogger(" [bootstrap] ");
        isSystemTask = false;
    }

    @Override
    public TaskImpl getTask() {
        return this;
    }

    @Override
    public TaskLogger log() {
        return logger;
    }

    @Override
    public ZeidonLogger dblog() {
        return dblogger;
    }

    /**
     * Adds the view to the internal weak viewList map.
     *
     * @param view
     */
    void addNewView(ViewImpl view) {
        viewList.put(view, Boolean.TRUE);
        if (log().isTraceEnabled())
            log().trace("ViewList count: %d/%d", viewList.size(), viewList.keySet().size());
    }

    void dropView(ViewImpl view) {
        // Try removing the view from all the name lists.  Note that this won't drop
        // the view name if it belongs to a different application list other than default.
        viewNameList.dropView(view);
        getSystemTask().viewNameList.dropView(view);
        getApplication().dropView(view);
        view.getApplication().dropView(view);

        // Don't remove the view from viewList.  It has a weak reference to the view so
        // it will eventually get cleaned up.  If the view doesn't disappear from the
        // viewList then the application must have a hold on the reference.  This will
        // make it easier to find resource leaks.
    }

    @Override
    public Collection<ViewImpl> getViewList() {
        // Return a copy of the view list.  This way the user application (most likely the
        //  browser) doesn't have to worry about views being deleted while iterating.
        ArrayList<ViewImpl> list = new ArrayList<ViewImpl>(viewList.keySet());
        Collections.sort(list);
        return Collections.unmodifiableList(list);
    }

    @Override
    public int getViewCount() {
        return viewList.keySet().size();
    }

    @Override
    public String getTaskId() {
        return taskId;
    }

    @Override
    public String getUserId() {
        return userId;
    }

    @Override
    public void setUserId(String userId) {
        this.userId = userId;
    }

    /**
     * Calls dropTask().  This is implemented to support Closeable interface.
     */
    @Override
    public void close() {
        dropTask();
    }

    @Override
    public synchronized void dropTask() {
        // If this has already been dropped return silently.
        if (!isValid)
            return;

        log().info("Dropping task %s", taskId);

        if (cleanupWork != null) {
            for (DropTaskCleanup work : cleanupWork)
                work.taskDropped(this);
        }

        isValid = false;

        assert assertDropTask();

        // All we need to do is remove the task from the Object Engine's task list.
        // The GC will do everything else.
        objectEngine.dropTask(this);
    }

    /**
     * @return
     */
    private boolean assertDropTask() {
        // Make sure that no views named at the application level are associated with this task.
        for (Application app : objectEngine.getApplicationList()) {
            for (View view : app.getAllNamedViews()) {
                if (!view.getTask().isValid()) {
                    getSystemTask().log().error(
                            "View %d is named at app level but is associated with an invalid task %d", view.getId(),
                            view.getTask().getTaskId());
                    assert false : "View named at app level is associated with a invalid task";
                }

                // Is this a valid check?
                if (view.getTask() != getSystemTask()) {
                    getSystemTask().log().error("View %d is associated with an invalid task %d", view.getId(),
                            view.getTask().getTaskId());
                    assert false : "There's a named view that is associated with a invalid task";
                }
            }
        }

        // If we get here then everythings ok.
        return true;
    }

    @Override
    public TaskImpl getSystemTask() {
        return objectEngine.getSystemTask();
    }

    @Override
    public int commitMultipleOis(CommitOptions options, View... views) {
        return commitMultipleOis(options, Arrays.asList(views));
    }

    /* (non-Javadoc)
     * @see com.quinsoft.zeidon.Task#commitMultipleOis(long, com.quinsoft.zeidon.dbhandler.DbConfiguration, com.quinsoft.zeidon.View[])
     */
    @Override
    public int commitMultipleOis(View... views) {
        return commitMultipleOis(new CommitOptions(this), Arrays.asList(views));
    }

    @Override
    public int commitMultipleOis(Collection<View> views) {
        return commitMultipleOis(new CommitOptions(this), views);
    }

    /* (non-Javadoc)
     * @see com.quinsoft.zeidon.Task#commitMultipleOis(long, com.quinsoft.zeidon.dbhandler.DbConfiguration, com.quinsoft.zeidon.View[])
     */
    @Override
    public int commitMultipleOis(CommitOptions options, Collection<View> views) {
        ArrayList<View> list = new ArrayList<View>();
        list.addAll(views);
        options.setViewList(list);
        CommitMultipleOIs committer = new CommitMultipleOIs(this, options, views);
        return committer.commit();
    }

    @Override
    public JavaObjectEngine getObjectEngine() {
        return objectEngine;
    }

    @Override
    public void dropNameForView(String name, View view) {
        viewNameList.dropNameForView(name, view);
    }

    @Override
    public View getViewByName(String name) {
        return viewNameList.getViewByName(name);
    }

    @Override
    public View getViewByKey(long key) {
        for (View v : viewList.keySet()) {
            if (v.getId() == key)
                return v;
        }

        return null;
    }

    @Override
    public void setNameForView(String name, View view) {
        viewNameList.setNameForView(name, view);
    }

    @Override
    public List<String> getViewNameList(View view) {
        return viewNameList.getAllViewNames(view);
    }

    @Override
    public String toString() {
        return String.format("Task %s app %s", getTaskId(), getApplication());
    }

    @Override
    public boolean isValid() {
        return isValid;
    }

    @Override
    public String getTempDirectory() {
        if (tempDir != null)
            return tempDir;

        tempDir = System.getProperty("java.io.tmpdir");
        if (!new File(tempDir).exists())
            throw new ZeidonException(
                    "Temp directory '%s' does not exist.  Create the directory or set ZEIDON_TEMP");

        tempDir = tempDir + File.separator;
        return tempDir;
    }

    @Override
    public int compareTo(TaskImpl o) {
        return taskId.compareTo(o.getTaskId());
    }

    @Override
    public boolean equals(Object o) {
        if (o instanceof TaskImpl)
            return compareTo((TaskImpl) o) == 0;

        return false;
    }

    long getNextVersionNumber() {
        //TODO: This should probably be set for the entire OE because we could have entities
        // from two different tasks with different versions.
        return versionCounter.incrementAndGet();
    }

    private void unlockAll(int length, Lock... locks) {
        assert length <= locks.length;

        for (int i = 0; i < length; i++) {
            try {
                locks[i].unlock();
            } catch (Throwable t) {
                try {
                    log().error("Error trying to unlock lock", t);
                } catch (Throwable tt) {
                    // We need to make sure we unlock everything so if we catch an error while writing the
                    // error message we'll swallow it so we can keep going.
                }
            }
        }
    }

    /* (non-Javadoc)
     * @see com.quinsoft.zeidon.Task#setDescription(java.lang.String)
     */
    @Override
    public void setDescription(String description) {
        this.description = description;
    }

    /* (non-Javadoc)
     * @see com.quinsoft.zeidon.Task#getDescription()
     */
    @Override
    public String getDescription() {
        return description;
    }

    @Override
    public SerializeOi serializeOi() {
        return new SerializeOi();
    }

    @SuppressWarnings("unchecked")
    @Override
    public synchronized ScalaHelper getScalaHelper() {
        if (scalaHelper == null) {
            String className = "com.quinsoft.zeidon.scala.ScalaHelperImpl";
            ClassLoader classLoader = getObjectEngine().getClassLoader(className);
            Class<ScalaHelper> operationsClass;
            try {
                operationsClass = (Class<ScalaHelper>) classLoader.loadClass(className);
                scalaHelper = operationsClass.newInstance();
                scalaHelper.setClassLoader(classLoader);
            } catch (ClassNotFoundException e) {
                throw new ZeidonException("Couldn't load %s.  Do you have zeidon-scala in your classpath?",
                        className);
            } catch (Exception e) {
                throw ZeidonException.wrapException(e);
            }
        }

        return scalaHelper;
    }

    @Override
    public synchronized EntityCache getEntityCache(EntityDef entityDef) {
        if (entityCacheMap != null) {
            EntityCache entityCache = entityCacheMap.get(entityDef.getErEntityToken());
            if (entityCache != null)
                return entityCache;
        }

        if (isSystemTask)
            return null;

        return getSystemTask().getEntityCache(entityDef);
    }

    @Override
    public synchronized boolean setEntityCache(EntityCache entityCache) {
        if (entityCacheMap == null)
            entityCacheMap = new HashMap<>();

        boolean b = entityCacheMap.containsKey(entityCache.getErEntityToken());
        entityCacheMap.put(entityCache.getErEntityToken(), entityCache);
        return b;
    }

    @Override
    public synchronized String readZeidonConfig(Application application, String group, String key,
            String defaultValue) {
        String g = normalizeGroup(group);

        // Check to see if the value has been overridden for this task.
        if (configOverrideMap != null) {
            Map<String, Map<String, String>> appMap = configOverrideMap.get(application);
            if (appMap != null) {
                Map<String, String> groupMap = appMap.get(group);
                if (groupMap != null) {
                    if (groupMap.containsKey(key))
                        return groupMap.get(key); // If we get here then the value has been overridden.
                }
            }
        }

        return getObjectEngine().getZeidonPreferences(application).get(g, key, defaultValue);
    }

    /**
     * Synchronized so it can be used for the system task.
     */
    @Override
    public synchronized void overrideZeidonConfig(Application application, String group, String key, String value) {
        group = normalizeGroup(group);

        if (configOverrideMap == null)
            configOverrideMap = new HashMap<>();

        Map<String, Map<String, String>> appMap = configOverrideMap.get(application);
        if (appMap == null) {
            appMap = new HashMap<>();
            configOverrideMap.put(application, appMap);
        }

        Map<String, String> groupMap = appMap.get(group);
        if (groupMap == null) {
            groupMap = new HashMap<>();
            appMap.put(group, groupMap);
        }

        groupMap.put(key, value);
    }

    @Override
    public void addTaskCleanupWork(DropTaskCleanup work) {
        if (cleanupWork == null)
            cleanupWork = new ArrayList<>();

        cleanupWork.add(work);
    }

    @Override
    public DeserializeOi deserializeOi() {
        return new DeserializeOi(this).setApplication(this.getApplication());
    }
}