org.nabucco.alfresco.enhScriptEnv.common.script.functions.AbstractLogFunction.java Source code

Java tutorial

Introduction

Here is the source code for org.nabucco.alfresco.enhScriptEnv.common.script.functions.AbstractLogFunction.java

Source

/*
 * Copyright 2015 PRODYNA AG
 *
 * Licensed under the Eclipse Public License (EPL), Version 1.0 (the "License"); you may not use
 * this file except in compliance with the License. You may obtain a copy of the License at
 *
 * https://www.eclipse.org/legal/epl-v10.html
 *
 * 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.nabucco.alfresco.enhScriptEnv.common.script.functions;

import java.lang.ref.WeakReference;
import java.text.MessageFormat;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.alfresco.util.Pair;
import org.alfresco.util.ParameterCheck;
import org.alfresco.util.PropertyCheck;
import org.nabucco.alfresco.enhScriptEnv.common.script.EnhancedScriptProcessor;
import org.nabucco.alfresco.enhScriptEnv.common.script.ReferenceScript;
import org.nabucco.alfresco.enhScriptEnv.common.script.ScopeContributor;
import org.nabucco.alfresco.enhScriptEnv.common.script.ReferenceScript.ReferencePathType;
import org.nabucco.alfresco.enhScriptEnv.common.script.converter.ValueConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;

/**
 * @author Axel Faust, <a href="http://www.prodyna.com">PRODYNA AG</a>
 */
public abstract class AbstractLogFunction implements InitializingBean, ScopeContributor {

    protected static class LoggerData {
        private Collection<Logger> loggers;
        private String explicitLogger;
        private boolean inheritLoggerContext = true;

        // avoid holding any native JS object in memory because it could in turn hold the key to scriptLoggerData map in memory
        private WeakReference<Object> scriptLogger;

        public LoggerData() {
            // NO-OP
        }

        /**
         * @return the loggers
         */
        public final Collection<Logger> getLoggers() {
            return this.loggers;
        }

        /**
         * @param loggers
         *            the loggers to set
         */
        public final void setLoggers(final Collection<Logger> loggers) {
            this.loggers = loggers;
        }

        /**
         * @return the explicitLogger
         */
        public final String getExplicitLogger() {
            return this.explicitLogger;
        }

        /**
         * @param explicitLogger
         *            the explicitLogger to set
         */
        public final void setExplicitLogger(final String explicitLogger) {
            this.explicitLogger = explicitLogger;
        }

        /**
         * @return the inheritLoggerContext
         */
        public final boolean isInheritLoggerContext() {
            return this.inheritLoggerContext;
        }

        /**
         * @param inheritLoggerContext
         *            the inheritLoggerContext to set
         */
        public final void setInheritLoggerContext(final boolean inheritLoggerContext) {
            this.inheritLoggerContext = inheritLoggerContext;
        }

        /**
         * @return the scriptLogger
         */
        public final Object getScriptLogger() {
            return this.scriptLogger != null ? this.scriptLogger.get() : null;
        }

        /**
         * @param scriptLogger
         *            the legacyLogger to set
         */
        public final void setScriptLogger(final Object scriptLogger) {
            this.scriptLogger = new WeakReference<Object>(scriptLogger);
        }
    }

    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractLogFunction.class);

    private static final Object DUMMY_SCOPE = new Object();

    /**
     * Logger data map for state management and logger caching
     */
    protected final Map<Object, Map<ReferenceScript, LoggerData>> scopeLoggerData = new WeakHashMap<Object, Map<ReferenceScript, LoggerData>>();

    /**
     * Parent scope registry for bottom-up lookup and including context script in which parent-child relation was registered
     */
    protected final Map<Object, Pair<WeakReference<Object>, ReferenceScript>> scopeParents = new WeakHashMap<Object, Pair<WeakReference<Object>, ReferenceScript>>();

    protected final ReadWriteLock scopeLoggerDataLock = new ReentrantReadWriteLock(true);

    protected final ReadWriteLock scopeParentLock = new ReentrantReadWriteLock(true);

    protected EnhancedScriptProcessor<? extends ReferenceScript> scriptProcessor;

    protected ValueConverter valueConverter;

    protected String defaultLoggerPrefix;

    /**
     *
     * {@inheritDoc}
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        PropertyCheck.mandatory(this, "scriptProcessor", this.scriptProcessor);
        PropertyCheck.mandatory(this, "valueConverter", this.valueConverter);
        PropertyCheck.mandatory(this, "defaultLoggerPrefix", this.defaultLoggerPrefix);

        this.scriptProcessor.registerScopeContributor(this);
    }

    /**
     * @param defaultLoggerPrefix
     *            the defaultLoggerPrefix to set
     */
    public final void setDefaultLoggerPrefix(final String defaultLoggerPrefix) {
        this.defaultLoggerPrefix = defaultLoggerPrefix;
    }

    /**
     * @param scriptProcessor
     *            the scriptProcessor to set
     */
    public final void setScriptProcessor(final EnhancedScriptProcessor<? extends ReferenceScript> scriptProcessor) {
        this.scriptProcessor = scriptProcessor;
    }

    /**
     * @param valueConverter
     *            the valueConverter to set
     */
    public final void setValueConverter(final ValueConverter valueConverter) {
        this.valueConverter = valueConverter;
    }

    protected Logger getLogger() {
        return LOGGER;
    }

    protected void handleOut(final String message) {
        final ReferenceScript contextScriptLocation = this.scriptProcessor.getContextScriptLocation();
        this.getLogger().warn("Script {} logging to System.out: {}", contextScriptLocation, message);
        System.out.println(message);
    }

    protected void handleRegisterChildScope(final Object parentScope, final Object childScope) {
        ParameterCheck.mandatory("parentScope", parentScope);
        ParameterCheck.mandatory("childScope", childScope);

        final ReferenceScript script = this.scriptProcessor.getContextScriptLocation();
        this.scopeParentLock.writeLock().lock();
        try {
            this.scopeParents.put(childScope, new Pair<WeakReference<Object>, ReferenceScript>(
                    new WeakReference<Object>(parentScope), script));
        } finally {
            this.scopeParentLock.writeLock().unlock();
        }
    }

    protected void handleSetLoggerInheritance(final Object scope, final boolean inheritLoggerContext) {
        final ReferenceScript referenceScript = this.scriptProcessor.getContextScriptLocation();
        final LoggerData loggerData = this.getLoggerData(scope, referenceScript, true);
        loggerData.setInheritLoggerContext(inheritLoggerContext);
    }

    protected void handleSetLogger(final Object scope, final String explicitLoggerName) {
        ParameterCheck.mandatory("scope", scope);
        ParameterCheck.mandatoryString("explicitLoggerName", explicitLoggerName);

        final ReferenceScript referenceScript = this.scriptProcessor.getContextScriptLocation();
        final LoggerData loggerData = this.getLoggerData(scope, referenceScript, true);

        if (loggerData.getLoggers() == null && loggerData.getExplicitLogger() == null) {
            loggerData.setExplicitLogger(explicitLoggerName);
        } else {
            if (loggerData.getExplicitLogger() != null) {
                throw new IllegalStateException("Explicit logger already set");
            } else if (loggerData.getLoggers() != null) {
                throw new IllegalStateException("Loggers already initialized");
            }
        }
    }

    protected Object getScriptLogger() {
        final ReferenceScript topLevelScript = this.scriptProcessor.getScriptCallChain().get(0);
        final LoggerData globalLoggerData = this.getLoggerData(DUMMY_SCOPE, topLevelScript, false);
        final Object scriptLogger = globalLoggerData != null ? globalLoggerData.getScriptLogger() : null;
        return scriptLogger;
    }

    protected void handleSetScriptLogger(final Object scriptLogger) {
        ParameterCheck.mandatory("scriptLogger", scriptLogger);

        // ScriptLogger is always treated as a top-level context data object, so retrieve the top-level logger data
        final ReferenceScript topLevelScript = this.scriptProcessor.getScriptCallChain().get(0);
        final LoggerData loggerData = this.getLoggerData(DUMMY_SCOPE, topLevelScript, true);
        final Object setScriptLogger = loggerData.getScriptLogger();
        if (setScriptLogger != scriptLogger && setScriptLogger != null) {
            if (!this.isUnreplaceableScriptLogger(setScriptLogger)) {
                throw new IllegalStateException(
                        "ScriptLogger has already been set - not allowed to replace it once set");
            }
        }
        loggerData.setScriptLogger(scriptLogger);
    }

    /**
     * Checks if the provided script logger is a script logger that may not be replaced with a custom script logger. This is to ensure that
     * only the default Alfresco / Surf script logger instances may be replaced by a custom script logger, but any request to replace an
     * already set custom script logger fails.
     *
     * @param scriptLogger
     *            the script logger to test
     * @return {@code true} if the script logger may not be replaced, {@code false} otherwise
     */
    abstract protected boolean isUnreplaceableScriptLogger(Object scriptLogger);

    protected Collection<Logger> getParentLoggers(final Object scope, final ReferenceScript script) {
        // determine parent script from call chain
        final List<? extends ReferenceScript> scriptCallChain = this.scriptProcessor.getScriptCallChain();
        final int scriptIndex = scriptCallChain.indexOf(script);
        final ReferenceScript parentScript = scriptIndex > 0 ? scriptCallChain.get(scriptIndex - 1) : null;

        // determine parent scope from explicit registration
        final Pair<WeakReference<Object>, ReferenceScript> scopeParentPair;

        this.scopeParentLock.readLock().lock();
        try {
            scopeParentPair = this.scopeParents.get(scope);
        } finally {
            this.scopeParentLock.readLock().unlock();
        }
        // use parent scope only if one has been registered and the script it was registered for is the identical script retrieved from the
        // call chain
        final Object parentScope = scopeParentPair == null || (scopeParentPair.getSecond() != parentScript) ? scope
                : scopeParentPair.getFirst().get();

        final LoggerData parentLoggerData = parentScope != null && parentScript != null
                ? this.getLoggerData(parentScope, parentScript, false)
                : null;
        final Collection<Logger> loggers;
        if (parentLoggerData == null || parentLoggerData.isInheritLoggerContext()) {
            loggers = this.getLoggers(parentScope, parentScript, parentLoggerData);
        } else {
            loggers = Collections.emptySet();
        }

        return loggers;
    }

    protected Collection<Logger> getLoggers(final Object scope, final boolean createIfNull) {
        final Collection<Logger> loggers;

        // TODO: what about logging in functions defined in script A but called from script B (due to scope access or explicit passing)?
        final ReferenceScript referenceScript = this.scriptProcessor.getContextScriptLocation();

        final LoggerData loggerData = this.getLoggerData(scope, referenceScript, createIfNull);
        loggers = this.getLoggers(scope, referenceScript, loggerData);

        return loggers;
    }

    protected Collection<Logger> getLoggers(final Object scope, final ReferenceScript referenceScript,
            final LoggerData loggerData) {
        final Collection<Logger> loggers;
        if (loggerData == null || loggerData.getLoggers() == null) {
            loggers = new HashSet<Logger>();
            if (referenceScript != null) {
                loggers.addAll(this.getParentLoggers(scope, referenceScript));
            }

            if (loggerData != null && loggerData.getExplicitLogger() != null) {
                loggers.add(LoggerFactory.getLogger(loggerData.getExplicitLogger()));
            } else if (!this.isParentLoggerExplicit(scope, referenceScript)) {
                // Note: We previously included the legacy script logger explicitly here, but this is now handled separately

                if (referenceScript != null) {
                    final Collection<ReferencePathType> supportedReferencePathTypes = referenceScript
                            .getSupportedReferencePathTypes();
                    for (final ReferencePathType referencePathType : supportedReferencePathTypes) {
                        final String referencePath = referenceScript.getReferencePath(referencePathType);
                        if (referencePath != null) {
                            final String loggerSuffix = referencePath.replace('.', '_').replace('/', '.');
                            final String loggerName = MessageFormat.format("{0}.{1}.{2}", this.defaultLoggerPrefix,
                                    referencePathType, loggerSuffix);
                            loggers.add(LoggerFactory.getLogger(loggerName));
                        }
                    }
                }
            }

            if (loggerData != null) {
                loggerData.setLoggers(loggers);
            }
        } else {
            loggers = loggerData.getLoggers();
        }
        return loggers;
    }

    protected boolean isParentLoggerExplicit(final Object scope, final ReferenceScript script) {
        // determine parent script from call chain
        final List<? extends ReferenceScript> scriptCallChain = this.scriptProcessor.getScriptCallChain();
        final int scriptIndex = scriptCallChain.indexOf(script);
        final ReferenceScript parentScript = scriptIndex > 0 ? scriptCallChain.get(scriptIndex - 1) : null;

        final boolean result;
        if (parentScript != null) {
            // determine parent scope from explicit registration
            final Pair<WeakReference<Object>, ReferenceScript> scopeParentPair;

            this.scopeParentLock.readLock().lock();
            try {
                scopeParentPair = this.scopeParents.get(scope);
            } finally {
                this.scopeParentLock.readLock().unlock();
            }

            // use parent scope only if one has been registered and the script it was registered for is the identical script retrieved from
            // the call chain
            final Object parentScope = scopeParentPair == null || (scopeParentPair.getSecond() != parentScript)
                    ? scope
                    : scopeParentPair.getFirst().get();

            final LoggerData parentLoggerData = parentScope != null
                    ? this.getLoggerData(parentScope, parentScript, false)
                    : null;

            // check immediate parent
            final boolean nextParentLoggerIsExplicit = parentLoggerData != null
                    && parentLoggerData.getExplicitLogger() != null && parentLoggerData.isInheritLoggerContext();
            // recursive check unless inheritance is off
            final boolean ancestorLoggerIsExplicit = (parentLoggerData == null
                    || parentLoggerData.isInheritLoggerContext())
                    && this.isParentLoggerExplicit(parentScope, parentScript);
            result = nextParentLoggerIsExplicit || ancestorLoggerIsExplicit;
        } else {
            result = false;
        }
        return result;
    }

    protected LoggerData getLoggerData(final Object scope, final ReferenceScript script,
            final boolean createIfNull) {
        ParameterCheck.mandatory("scope", scope);
        ParameterCheck.mandatory("script", script);

        LoggerData loggerData = null;

        final Map<ReferenceScript, LoggerData> loggerDataByScript = this.getScriptLoggerDataForContext(scope,
                createIfNull);
        loggerData = loggerDataByScript != null ? loggerDataByScript.get(script) : null;
        if (loggerDataByScript != null && loggerData == null && createIfNull) {
            loggerData = this.createLoggerData();
            loggerDataByScript.put(script, loggerData);
        }

        if (loggerData == null) {
            // determine parent scope from explicit registration
            final Pair<WeakReference<Object>, ReferenceScript> scopeParentPair;

            this.scopeParentLock.readLock().lock();
            try {
                scopeParentPair = this.scopeParents.get(scope);
            } finally {
                this.scopeParentLock.readLock().unlock();
            }
            if (scopeParentPair != null) {
                final Object parentScope = scopeParentPair.getFirst().get();
                if (parentScope != null) {
                    loggerData = this.getLoggerData(parentScope, script, createIfNull);
                }
            }
        }
        return loggerData;
    }

    protected LoggerData createLoggerData() {
        return new LoggerData();
    }

    protected Map<ReferenceScript, LoggerData> getScriptLoggerDataForContext(final Object scope,
            final boolean createIfNull) {
        Map<ReferenceScript, LoggerData> dataByScript = null;
        this.scopeLoggerDataLock.readLock().lock();
        try {
            dataByScript = this.scopeLoggerData.get(scope);
        } finally {
            this.scopeLoggerDataLock.readLock().unlock();
        }

        if (dataByScript == null && createIfNull) {
            dataByScript = new IdentityHashMap<ReferenceScript, LoggerData>();
            this.scopeLoggerDataLock.writeLock().lock();
            try {
                this.scopeLoggerData.put(scope, dataByScript);
            } finally {
                this.scopeLoggerDataLock.writeLock().unlock();
            }
        }

        return dataByScript;
    }
}