org.auraframework.impl.clientlibrary.ClientLibraryServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.auraframework.impl.clientlibrary.ClientLibraryServiceImpl.java

Source

/*
 * 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.clientlibrary;

import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.auraframework.Aura;
import org.auraframework.cache.Cache;
import org.auraframework.clientlibrary.ClientLibraryResolver;
import org.auraframework.clientlibrary.ClientLibraryResolverRegistry;
import org.auraframework.clientlibrary.ClientLibraryService;
import org.auraframework.clientlibrary.Combinable;
import org.auraframework.def.ClientLibraryDef;
import org.auraframework.def.DefDescriptor;
import org.auraframework.def.ResourceDef;
import org.auraframework.ds.serviceloader.AuraServiceProvider;
import org.auraframework.impl.system.DefDescriptorImpl;
import org.auraframework.system.AuraContext;
import org.auraframework.throwable.AuraRuntimeException;
import org.auraframework.throwable.NoContextException;
import org.auraframework.throwable.quickfix.ClientLibraryException;
import org.auraframework.throwable.quickfix.QuickFixException;
import org.auraframework.util.AuraTextUtil;

//import com.google.common.cache.Cache;
//import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

import aQute.bnd.annotation.component.Component;

/**
 * Service for including external client libraries (CSS or JS)
 */
@Component(provide = AuraServiceProvider.class)
public class ClientLibraryServiceImpl implements ClientLibraryService {

    // TODO: osgi - convert this to DS references
    private static Cache<String, String> getOutputCache() {
        return Aura.getCachingService().getClientLibraryOutputCache();
    }

    private static Cache<String, Set<String>> getUrlsCache() {
        return Aura.getCachingService().getClientLibraryUrlsCache();
    }

    public ClientLibraryServiceImpl() {
    }

    /**
     * Gets resolver for resolution. Empty string if none
     *
     * @return resolved url or null
     */
    @Override
    public String getResolvedUrl(ClientLibraryDef clientLibrary) {
        if (clientLibrary == null) {
            return null;
        }

        String url = clientLibrary.getUrl();
        if (StringUtils.isBlank(url)) {
            // Use resolver if url is blank
            ClientLibraryResolver resolver = getResolver(clientLibrary);
            if (resolver != null) {
                url = resolver.getUrl();
            }
        }
        return url;
    }

    /**
     * Checks whether client library can be combined.
     *
     * @param clientLibrary client library
     * @return true if combinable
     */
    @Override
    public boolean canCombine(ClientLibraryDef clientLibrary) throws QuickFixException {

        if (clientLibrary == null) {
            return false;
        }

        if (StringUtils.isBlank(clientLibrary.getUrl())) {
            // Use resolver if url is blank
            ClientLibraryResolver resolver = getResolver(clientLibrary);
            if (resolver != null) {
                return clientLibrary.shouldCombine() && resolver.canCombine();
            } else {
                throw new ClientLibraryException(
                        "Client library must have resolver if url is blank: " + clientLibrary.getLibraryName(),
                        clientLibrary.getLocation());
            }
        }

        return clientLibrary.shouldCombine();
    }

    /**
     * Write resources.css
     *
     * @param output output
     * @throws IOException
     * @throws QuickFixException
     */
    @Override
    public void writeCss(AuraContext context, Appendable output) throws IOException, QuickFixException {
        write(context, ClientLibraryDef.Type.CSS, output);
    }

    /**
     * Write resources.js
     *
     * @param output output
     * @throws IOException
     * @throws QuickFixException
     */
    @Override
    public void writeJs(AuraContext context, Appendable output) throws IOException, QuickFixException {
        write(context, ClientLibraryDef.Type.JS, output);
    }

    @Override
    public ClientLibraryResolverRegistry getResolverRegistry() {
        return ClientLibraryResolverRegistryImpl.INSTANCE;
    }

    /**
     * Returns urls that should be requested. All combined client libraries are in resources.(css|js) while other has
     * their own entry
     *
     * @param type CSS or JS
     * @return urls
     * @throws QuickFixException
     */
    @Override
    public Set<String> getUrls(AuraContext context, ClientLibraryDef.Type type) throws QuickFixException {

        if (context == null) {
            throw new NoContextException();
        }

        AuraContext.Mode mode = context.getMode();
        String uid = context.getUid(context.getApplicationDescriptor());
        String contextPath = context.getContextPath();
        String nonce = context.getFrameworkUID();

        if (uid == null) {
            return Collections.emptySet();
        }

        //
        // TODO: rethink this caching. It has an awful lot in the keys. Maybe we can prepend the
        // 'prefix' string on output if it is a relative URL.
        //
        String key = new StringBuilder(uid).append(":").append(type).append(":").append(mode).append(":")
                .append(nonce).append(":").append(contextPath).toString();
        Set<String> urls = getUrlsCache().getIfPresent(key);

        if (urls == null) {

            List<ClientLibraryDef> clientLibs = getClientLibraries(context, type);
            urls = Sets.newLinkedHashSet();

            boolean hasCombines = false;
            String url = null;

            for (ClientLibraryDef clientLib : clientLibs) {

                if (canCombine(clientLib)) {
                    hasCombines = true;
                } else {
                    // add url to list when client library is not combined
                    url = getResolvedUrl(clientLib);
                }

                if (StringUtils.isNotBlank(url)) {
                    urls.add(url);
                }

            }

            if (hasCombines) {
                // all combinable resources are put into resources.css or resources.js
                urls.add(getResourcesPath(context, type));
            }

            getUrlsCache().put(key, urls);
        }

        return urls;

    }

    /**
     * Writes resources css or js. Gets client libraries that should be combined and is written by their format adapter
     *
     * @param type CSS or JS
     * @param output output
     * @throws IOException
     * @throws QuickFixException
     */
    private void write(AuraContext context, ClientLibraryDef.Type type, Appendable output)
            throws IOException, QuickFixException {
        if (output == null) {
            throw new AuraRuntimeException("Output cannot be null");
        }

        if (context == null) {
            throw new NoContextException();
        }

        AuraContext.Mode mode = context.getMode();
        String uid = context.getUid(context.getApplicationDescriptor());

        String key = makeCacheKey(uid, mode, type);
        String code = getOutputCache().getIfPresent(key);

        if (code == null) {
            // no cache yet
            List<ClientLibraryDef> clientLibs = getClientLibraries(context, type);
            Set<Combinable> combinables = Sets.newLinkedHashSet();
            StringBuilder sb = new StringBuilder();

            for (ClientLibraryDef clientLib : clientLibs) {
                if (canCombine(clientLib)) {
                    Combinable combinable = getCombinable(clientLib);
                    if (combinable != null) {
                        combinables.add(combinable);
                    }
                }
            }

            if (!combinables.isEmpty()) {
                // ClientLibraryCSSFormatAdapter or ClientLibraryJSFormatAdapter
                Aura.getSerializationService().writeCollection(combinables, Combinable.class, sb, type.toString());
            }

            code = sb.toString();
            getOutputCache().put(key, code);
        }

        output.append(code);
    }

    /**
     * Convenience wrapper to get corresponding resolver from registry
     *
     * @param clientLibrary client library
     * @return resolver for client library
     */
    private ClientLibraryResolver getResolver(ClientLibraryDef clientLibrary) {
        return getResolverRegistry().get(clientLibrary.getLibraryName(), clientLibrary.getType());
    }

    /**
     * Loops through all client library definitions of current application and filters by type and current mode
     *
     * @param context current aura context
     * @param type CSS or JS
     * @return list
     */
    private static List<ClientLibraryDef> getClientLibraries(AuraContext context, ClientLibraryDef.Type type) {
        AuraContext.Mode mode = context.getMode();
        String uid = context.getUid(context.getApplicationDescriptor());
        // get all client libraries specified for current app (cmp).
        List<ClientLibraryDef> clientLibs = context.getDefRegistry().getClientLibraries(uid);
        List<ClientLibraryDef> ret = Lists.newArrayList();

        if (clientLibs != null) {
            for (ClientLibraryDef clientLib : clientLibs) {
                // check if client library should be added based on type and current mode
                if (clientLib.shouldInclude(mode, type)) {
                    // check for duplicate client library
                    if (!ret.contains(clientLib)) {
                        ret.add(clientLib);
                    }
                }
            }
        }

        return ret;
    }

    /**
     * Calculates resources css or js url ie /l/[context]/resources.css
     *
     * @param type CSS or JS
     * @return
     * @throws QuickFixException
     */
    private static String getResourcesPath(AuraContext context, ClientLibraryDef.Type type)
            throws QuickFixException {
        String contextPath = context.getContextPath();
        StringBuilder path = new StringBuilder(contextPath).append("/l/");
        StringBuilder sb = new StringBuilder();

        try {
            Aura.getSerializationService().write(context, null, AuraContext.class, sb, "HTML");
        } catch (IOException e) {
            throw new AuraRuntimeException(e);
        }

        String contextJson = AuraTextUtil.urlencode(sb.toString());
        path.append(contextJson).append("/resources.").append(type.toString().toLowerCase());

        return path.toString();
    }

    /**
     * Creates cache key using the current uid, mode, and type. Caches uncompressed version for DEV or TEST modes,
     * compressed otherwise
     *
     * @param uid application uid
     * @param mode current aura mode
     * @param type CSS or JS
     * @return cache key
     */
    private static String makeCacheKey(String uid, AuraContext.Mode mode, ClientLibraryDef.Type type) {
        StringBuilder key = new StringBuilder();
        key.append(uid).append(":").append(type).append(":");

        if (mode.prettyPrint()) {
            key.append("DEV");
        } else {
            key.append("MIN");
        }
        return key.toString();
    }

    /**
     * Get Combinable to allow getting contents
     *
     * @param clientLibrary client library
     * @return combinable
     * @throws QuickFixException
     */
    private Combinable getCombinable(ClientLibraryDef clientLibrary) throws QuickFixException {
        String url = clientLibrary.getUrl();
        Combinable combinable = null;
        if (StringUtils.isBlank(url)) {
            ClientLibraryResolver resolver = getResolver(clientLibrary);
            if (resolver != null && resolver.canCombine()) {
                // combinable resolver
                combinable = (Combinable) resolver;
            }
        } else if (StringUtils.startsWithIgnoreCase(url, DefDescriptor.CSS_PREFIX + "://")
                || StringUtils.startsWithIgnoreCase(url, DefDescriptor.JAVASCRIPT_PREFIX + "://")) {
            // if url is qualified name of DefDescriptor<ResourceDef>
            DefDescriptor<ResourceDef> descriptor = DefDescriptorImpl.getInstance(url, ResourceDef.class);
            if (descriptor.exists()) {
                ResourceDef def = descriptor.getDef();
                if (def != null) {
                    combinable = (Combinable) def;
                }
            }
        }
        return combinable;
    }
}