Package org.auraframework.impl.source

Source Code of org.auraframework.impl.source.StringSourceLoader

/*
* Copyright (C) 2013 salesforce.com, inc.
*
* 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 org.auraframework.impl.source;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import javax.annotation.Nullable;

import org.auraframework.Aura;
import org.auraframework.def.ApplicationDef;
import org.auraframework.def.ComponentDef;
import org.auraframework.def.ControllerDef;
import org.auraframework.def.DefDescriptor;
import org.auraframework.def.DefDescriptor.DefType;
import org.auraframework.def.Definition;
import org.auraframework.def.DescriptorFilter;
import org.auraframework.def.DesignDef;
import org.auraframework.def.DocumentationDef;
import org.auraframework.def.EventDef;
import org.auraframework.def.HelperDef;
import org.auraframework.def.IncludeDef;
import org.auraframework.def.InterfaceDef;
import org.auraframework.def.LayoutsDef;
import org.auraframework.def.LibraryDef;
import org.auraframework.def.ModelDef;
import org.auraframework.def.NamespaceDef;
import org.auraframework.def.ProviderDef;
import org.auraframework.def.RendererDef;
import org.auraframework.def.StyleDef;
import org.auraframework.def.TestSuiteDef;
import org.auraframework.def.ThemeDef;
import org.auraframework.system.Parser.Format;
import org.auraframework.system.PrivilegedNamespaceSourceLoader;
import org.auraframework.system.Source;
import org.auraframework.system.SourceListener.SourceMonitorEvent;
import org.auraframework.system.SourceLoader;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

/**
* This source loader allows tests to load and unload source from strings.
*
* This loader is a singleton to ensure that it can be authoritative for the "string" namespace.
*
* FIXME: W-1933490!!!! The namespaces map is very dangerous here, as it is mutable in ways that aura does not expect.
* There is a lock to ensure that the read/modify/write operations that are used by source 'put' methods are atomic, but
* that does not guarantee coherency. In particular, we may lie to aura and say that we have namespaces that we don't,
* or provide descriptors via find that aura will not be able to find because it has a fixed idea of the namespaces
* represented. This could be fixed by providing a fixed view into the namespaces provided.
*
*/
public class StringSourceLoader implements SourceLoader, PrivilegedNamespaceSourceLoader {
    public static final String DEFAULT_NAMESPACE = "string";
    public static final String OTHER_NAMESPACE = "string1";
    public static final String DEFAULT_CUSTOM_NAMESPACE = "cstring";
    public static final String OTHER_CUSTOM_NAMESPACE = "cstring1";

    private static final String DEFAULT_NAME_PREFIX = "thing";
    private static final Set<String> PREFIXES = ImmutableSet.of(
            DefDescriptor.MARKUP_PREFIX, DefDescriptor.JAVASCRIPT_PREFIX,
            DefDescriptor.CSS_PREFIX, DefDescriptor.TEMPLATE_CSS_PREFIX);
    private static final Set<DefType> DEFTYPES = ImmutableSet.of(
            DefType.APPLICATION, DefType.COMPONENT, DefType.EVENT, DefType.LIBRARY,
            DefType.INCLUDE, DefType.INTERFACE, DefType.LAYOUTS, DefType.CONTROLLER,
            DefType.HELPER, DefType.NAMESPACE, DefType.RENDERER, DefType.STYLE,
            DefType.TESTSUITE, DefType.RESOURCE, DefType.DESIGN);

    /**
     * A counter that we can use to guarantee unique names across multiple calls to add a source.
     */
    private static AtomicLong counter = new AtomicLong();
    private static Lock nsLock = new ReentrantLock();

    /**
     * A helper to hold the singleton instance.
     */
    private static class SingletonHolder {
        private static final StringSourceLoader INSTANCE = new StringSourceLoader();
    }

    public static StringSourceLoader getInstance() {
        return SingletonHolder.INSTANCE;
    }

    /**
     * This map stores all of the sources owned by this loader, split into namespaces.
     */
    private final Map<String, Map<DefDescriptor<? extends Definition>, StringSource<? extends Definition>>> namespaces = new ConcurrentHashMap<>();

    private final Map<String, Map<DefDescriptor<? extends Definition>, StringSource<? extends Definition>>> customNamespaces = new ConcurrentHashMap<>();

    private StringSourceLoader() {
        namespaces.put(DEFAULT_NAMESPACE,
                new ConcurrentHashMap<DefDescriptor<? extends Definition>, StringSource<? extends Definition>>());
        namespaces.put(OTHER_NAMESPACE,
                new ConcurrentHashMap<DefDescriptor<? extends Definition>, StringSource<? extends Definition>>());

        customNamespaces.put(DEFAULT_CUSTOM_NAMESPACE,
                new ConcurrentHashMap<DefDescriptor<? extends Definition>, StringSource<? extends Definition>>());
        customNamespaces.put(OTHER_CUSTOM_NAMESPACE,
                new ConcurrentHashMap<DefDescriptor<? extends Definition>, StringSource<? extends Definition>>());
    }

    /**
     * Generate a {@link DefDescriptor} with a unique name. If namePrefix does not contain a namespace, the descriptor
     * will be created in the 'string' namespace. If namePrefix does not contain the name portion (i.e. it is null,
     * empty, or just a namespace with the trailing delimiter), 'thing' will be used as the base name.
     *
     * @param namePrefix if non-null, then generate some name with the given prefix for the descriptor.
     * @param defClass the interface of the type definition
     * @return a {@link DefDescriptor} with name that is guaranteed to be unique in the string: namespace.
     */
    public final <D extends Definition, B extends Definition> DefDescriptor<D> createStringSourceDescriptor(
            @Nullable String namePrefix, Class<D> defClass, @Nullable DefDescriptor<B> bundle) {

        DescriptorInfo descriptorInfo = DescriptorInfo.get(defClass);

        String namespace;
        String name;
        if (namePrefix == null || namePrefix.isEmpty()) {
            namespace = DEFAULT_NAMESPACE;
            name = DEFAULT_NAME_PREFIX;
        } else {
            int idx = namePrefix.indexOf(descriptorInfo.getDelimiter());
            if (idx < 0) {
                namespace = DEFAULT_NAMESPACE;
                name = namePrefix;
            } else if (idx == namePrefix.length() - 1) {
                namespace = namePrefix.substring(0, idx);
                name = DEFAULT_NAME_PREFIX;
            } else {
                namespace = namePrefix.substring(0, idx);
                name = namePrefix.substring(idx + 1);
            }
        }

        return descriptorInfo.getDescriptor(namespace, name + counter.incrementAndGet(), bundle);
    }

    /**
     * Load a new definition.
     *
     * @param defClass the definition class that this source will represent
     * @param contents the source contents
     * @param namePrefix if non-null, then generate some name with the given prefix for the descriptor.
     * @param isPrivilegedNamespace if true, namespace is privileged
     * @return the created {@link StringSource}
     * @throws IllegalStateException when loading a definition that already exists with the same descriptor.
     */
    public final <D extends Definition> StringSource<D> addSource(Class<D> defClass, String contents,
            @Nullable String namePrefix, boolean isPrivilegedNamespace) {
        return putSource(defClass, contents, namePrefix, false, isPrivilegedNamespace);
    }

    /**
     * Load a definition.
     *
     * @param defClass the definition class that this source will represent
     * @param contents the source contents
     * @param namePrefix if non-null, then generate some name with the given prefix for the descriptor.
     * @param overwrite if true, overwrite any previously loaded definition
     * @param isPrivilegedNamespace if true, namespace is privileged
     * @return the created {@link StringSource}
     */
    public final <D extends Definition> StringSource<D> putSource(Class<D> defClass, String contents,
            @Nullable String namePrefix, boolean overwrite, boolean isPrivilegedNamespace) {
        return putSource(defClass, contents, namePrefix, overwrite, isPrivilegedNamespace, null);
    }

    /**
     * Load a definition.
     *
     * @param defClass the definition class that this source will represent
     * @param contents the source contents
     * @param namePrefix if non-null, then generate some name with the given prefix for the descriptor.
     * @param overwrite if true, overwrite any previously loaded definition
     * @param isPrivilegedNamespace if true, namespace is privileged
     * @return the created {@link StringSource}
     */
    public final <D extends Definition, B extends Definition> StringSource<D> putSource(Class<D> defClass, String contents,
            @Nullable String namePrefix, boolean overwrite, boolean isPrivilegedNamespace, @Nullable DefDescriptor<B> bundle) {
        DefDescriptor<D> descriptor = createStringSourceDescriptor(namePrefix, defClass, bundle);
        return putSource(descriptor, contents, overwrite, isPrivilegedNamespace);
    }

    /**
     * Load a definition.
     *
     * @param descriptor the DefDescriptor key for the loaded definition
     * @param contents the source contents
     * @param overwrite if true, overwrite any previously loaded definition
     * @return the created {@link StringSource}
     */
    public final <D extends Definition> StringSource<D> putSource(DefDescriptor<D> descriptor, String contents,
            boolean overwrite) {
        return putSource(descriptor, contents, overwrite, true);
    }

    /**
     * Load a definition.
     *
     * @param descriptor the DefDescriptor key for the loaded definition
     * @param contents the source contents
     * @param overwrite if true, overwrite any previously loaded definition
     * @param isPrivilegedNamespace if true, namespace is privileged
     * @return the created {@link StringSource}
     */
    public final <D extends Definition> StringSource<D> putSource(DefDescriptor<D> descriptor, String contents,
            boolean overwrite, boolean isPrivilegedNamespace) {
        Format format = DescriptorInfo.get(descriptor.getDefType().getPrimaryInterface()).getFormat();
        StringSource<D> source = new StringSource<>(descriptor, contents, descriptor.getQualifiedName(), format);
        return putSource(descriptor, source, overwrite, isPrivilegedNamespace);
    }

    private final <D extends Definition> StringSource<D> putSource(DefDescriptor<D> descriptor,
            StringSource<D> source, boolean overwrite, boolean isPrivilegedNamespace) {
        SourceMonitorEvent event = SourceMonitorEvent.CREATED;

        nsLock.lock();
        try {
            String namespace = descriptor.getNamespace();
            Map<DefDescriptor<? extends Definition>, StringSource<? extends Definition>> sourceMap;

            if (isPrivilegedNamespace) {
                sourceMap = namespaces.get(namespace);
            }
            else {
                sourceMap = customNamespaces.get(namespace);
            }

            if (sourceMap == null) {
                sourceMap = Maps.newHashMap();
                if (isPrivilegedNamespace) {
                    namespaces.put(namespace, sourceMap);
                }
                else {
                    customNamespaces.put(namespace, sourceMap);
                }
            } else {
                boolean containsKey = sourceMap.containsKey(descriptor);
                Preconditions.checkState(overwrite || !containsKey);
                if (containsKey) {
                    event = SourceMonitorEvent.CHANGED;
                }

            }
            sourceMap.put(descriptor, source);
        } finally {
            nsLock.unlock();
        }
        // notify source listeners of change
        Aura.getDefinitionService().onSourceChanged(descriptor, event, null);

        return source;
    }

    /**
     * Remove a definition from the source loader.
     *
     * @param descriptor the descriptor identifying the loaded definition to remove.
     */
    public final void removeSource(DefDescriptor<?> descriptor) {
        nsLock.lock();
        try {
            String namespace = descriptor.getNamespace();
            Map<DefDescriptor<? extends Definition>, StringSource<? extends Definition>> sourceMap;

            if (namespaces.containsKey(namespace)) {
                sourceMap = namespaces.get(namespace);
                Preconditions.checkState(sourceMap != null);
                Preconditions.checkState(sourceMap.remove(descriptor) != null);
                if (!DEFAULT_NAMESPACE.equals(namespace) && !OTHER_NAMESPACE.equals(namespace) && sourceMap.isEmpty()) {
                    namespaces.remove(namespace);
                }
            }
            else if (customNamespaces.containsKey(namespace)) {
                sourceMap = customNamespaces.get(namespace);
                Preconditions.checkState(sourceMap != null);
                Preconditions.checkState(sourceMap.remove(descriptor) != null);
                if (!DEFAULT_CUSTOM_NAMESPACE.equals(namespace) && !OTHER_CUSTOM_NAMESPACE.equals(namespace)
                        && sourceMap.isEmpty()) {
                    customNamespaces.remove(namespace);
                }
            }
        } finally {
            nsLock.unlock();
        }
        // notify source listeners of change
        Aura.getDefinitionService().onSourceChanged(descriptor, SourceMonitorEvent.DELETED, null);
    }

    /**
     * Remove a definition from the source loader.
     *
     * @param source the loaded definition to remove.
     */
    public final void removeSource(StringSource<?> source) {
        removeSource(source.getDescriptor());
    }

    @Override
    public Set<DefDescriptor<?>> find(DescriptorFilter matcher) {
        Set<DefDescriptor<?>> ret = Sets.newHashSet();
        for (String namespace : namespaces.keySet()) {
            if (matcher.matchNamespace(namespace)) {
                for (DefDescriptor<?> desc : namespaces.get(namespace).keySet()) {
                    if (matcher.matchDescriptorNoNS(desc)) {
                        ret.add(desc);
                    }
                }
            }
        }
        for (String namespace : customNamespaces.keySet()) {
            if (matcher.matchNamespace(namespace)) {
                for (DefDescriptor<?> desc : customNamespaces.get(namespace).keySet()) {
                    if (matcher.matchDescriptorNoNS(desc)) {
                        ret.add(desc);
                    }
                }
            }
        }
        return ret;
    }

    @SuppressWarnings("unchecked")
    @Override
    public <D extends Definition> Set<DefDescriptor<D>> find(Class<D> primaryInterface, String prefix,
            String namespace) {
        Set<DefDescriptor<D>> ret = Sets.newHashSet();

        Map<DefDescriptor<? extends Definition>, StringSource<? extends Definition>> sourceMap = null;

        if (namespaces.containsKey(namespace)) {
            sourceMap = namespaces.get(namespace);
        }
        else if (customNamespaces.containsKey(namespace)) {
            sourceMap = customNamespaces.get(namespace);
        }

        if (sourceMap != null) {
            for (DefDescriptor<? extends Definition> desc : sourceMap.keySet()) {
                if (desc.getDefType().getPrimaryInterface() == primaryInterface && desc.getPrefix().equals(prefix)) {
                    ret.add((DefDescriptor<D>) desc);
                }
            }
        }
        return ret;
    }

    @Override
    public Set<DefType> getDefTypes() {
        return DEFTYPES;
    }

    @Override
    public Set<String> getNamespaces() {
        Set<String> allNamespaces = new HashSet<>(namespaces.keySet());
        Set<String> cNamespaces = new HashSet<>(customNamespaces.keySet());
        allNamespaces.addAll(cNamespaces);
        return ImmutableSet.copyOf(allNamespaces);
    }

    @Override
    public Set<String> getPrefixes() {
        return PREFIXES;
    }

    @SuppressWarnings("unchecked")
    @Override
    public <D extends Definition> Source<D> getSource(DefDescriptor<D> descriptor) {
        Map<DefDescriptor<? extends Definition>, StringSource<? extends Definition>> sourceMap = null;

        if (namespaces.containsKey(descriptor.getNamespace())) {
            sourceMap = namespaces.get(descriptor.getNamespace());
        }
        else if (customNamespaces.containsKey(descriptor.getNamespace())) {
            sourceMap = customNamespaces.get(descriptor.getNamespace());
        }

        if (sourceMap != null) {
            StringSource<D> ret = (StringSource<D>) sourceMap.get(descriptor);
            if (ret != null) {
                // return a copy of the StringSource to emulate other Sources (hash is reset)
                return new StringSource<>(ret);
            }
            if (descriptor.getDefType().equals(DefType.NAMESPACE)) {
                Format format = DescriptorInfo.get(descriptor.getDefType().getPrimaryInterface()).getFormat();
                return new StringSource<>(descriptor, "", descriptor.getQualifiedName(),
                        format);
            }
        }
        return null;
    }

    private static enum DescriptorInfo {
        APPLICATION(ApplicationDef.class, Format.XML, DefDescriptor.MARKUP_PREFIX, ":"),
        COMPONENT(ComponentDef.class, Format.XML, DefDescriptor.MARKUP_PREFIX, ":"),
        EVENT(EventDef.class, Format.XML, DefDescriptor.MARKUP_PREFIX, ":"),
        LIBRARY(LibraryDef.class, Format.XML, DefDescriptor.MARKUP_PREFIX, ":"),
        INTERFACE(InterfaceDef.class, Format.XML, DefDescriptor.MARKUP_PREFIX, ":"),
        LAYOUTS(LayoutsDef.class, Format.XML, DefDescriptor.MARKUP_PREFIX, ":"),
        NAMESPACE(NamespaceDef.class, Format.XML, DefDescriptor.MARKUP_PREFIX, ""),
        CONTROLLER(ControllerDef.class, Format.JS, DefDescriptor.JAVASCRIPT_PREFIX, "."),
        HELPER(HelperDef.class, Format.JS, DefDescriptor.JAVASCRIPT_PREFIX, "."),
        MODEL(ModelDef.class, Format.JS, DefDescriptor.JAVASCRIPT_PREFIX, "."),
        PROVIDER(ProviderDef.class, Format.JS, DefDescriptor.JAVASCRIPT_PREFIX, "."),
        RENDERER(RendererDef.class, Format.JS, DefDescriptor.JAVASCRIPT_PREFIX, "."),
        STYLE(StyleDef.class, Format.CSS, DefDescriptor.CSS_PREFIX, "."),
        TESTSUITE(TestSuiteDef.class, Format.JS, DefDescriptor.JAVASCRIPT_PREFIX, "."),
        DOCUMENTATION(DocumentationDef.class, Format.XML, DefDescriptor.MARKUP_PREFIX, ":"),
        THEME(ThemeDef.class, Format.XML, DefDescriptor.MARKUP_PREFIX, ":"),
        DESIGN(DesignDef.class, Format.XML, DefDescriptor.MARKUP_PREFIX, ":"),
        INCLUDE(IncludeDef.class, Format.JS, DefDescriptor.JAVASCRIPT_PREFIX, ".");

        private static Map<Class<? extends Definition>, DescriptorInfo> infoMap;

        private final Class<? extends Definition> defClass;
        private final Format format;
        private final String prefix;
        private final String delimiter;

        private <D extends Definition> DescriptorInfo(Class<D> defClass, Format format, String prefix, String delimiter) {
            this.defClass = defClass;
            this.format = format;
            this.prefix = prefix;
            this.delimiter = delimiter;
            map(defClass, this);
        }

        private void map(Class<? extends Definition> defClass, DescriptorInfo info) {
            if (infoMap == null) {
                infoMap = Maps.newHashMap();
            }
            infoMap.put(defClass, info);
        }

        private static DescriptorInfo get(Class<? extends Definition> defClass) {
            return infoMap.get(defClass);
        }

        @SuppressWarnings("unchecked")
        private <D extends Definition, B extends Definition> DefDescriptor<D> getDescriptor(String namespace,
                String name, DefDescriptor<B> bundle) {
            return (DefDescriptor<D>) Aura.getDefinitionService()
                    .getDefDescriptor(
                            String.format("%s://%s%s%s", prefix, namespace,
                                    delimiter, name == null ? "" : name),
                            defClass, bundle);
        }

        private Format getFormat() {
            return format;
        }

        private String getDelimiter() {
            return delimiter;
        }
    }

    @Override
    public boolean isPrivilegedNamespace(String namespace) {
        return namespace != null && namespaces.containsKey(namespace);
    }
}
TOP

Related Classes of org.auraframework.impl.source.StringSourceLoader

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.