com.ibm.jaggr.service.impl.config.BundleVersionsHash.java Source code

Java tutorial

Introduction

Here is the source code for com.ibm.jaggr.service.impl.config.BundleVersionsHash.java

Source

/*
 * (C) Copyright 2012, IBM Corporation
 *
 * 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.ibm.jaggr.service.impl.config;

import com.ibm.jaggr.core.IAggregator;
import com.ibm.jaggr.core.IAggregatorExtension;
import com.ibm.jaggr.core.IExtensionInitializer;
import com.ibm.jaggr.core.IServiceRegistration;
import com.ibm.jaggr.core.NotFoundException;
import com.ibm.jaggr.core.config.IConfigScopeModifier;

import com.ibm.jaggr.service.IBundleResolver;
import com.ibm.jaggr.service.impl.AggregatorImpl;
import com.ibm.jaggr.service.util.BundleResolverFactory;

import org.apache.commons.codec.binary.Base64;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.NativeArray;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.osgi.framework.Bundle;

import java.lang.reflect.Method;
import java.security.MessageDigest;
import java.util.Collections;
import java.util.Dictionary;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * An implementation of {@link IConfigScopeModifier} that provides a JavaScript function which will
 * return a hash of the aggregated <code>Bundle-Version</code> header values for the list of bundles
 * specified in the function arguments. If the first argument to the function is an array of
 * strings, then the first argument specifies the names of the bundle headers that should be
 * included in the hash, followed by the bundle names.
 * <p>
 * Syntax is :
 * <pre>
 *      getBundleVersionsHash('com.acme.bundle1', 'com.acme.bundle2', ...);
 * </pre>
 * or
 * <pre>
 *      getBundleVersionsHash(['Bundle-Version', 'Bnd-LastModified'], 'com.acme.bundle1', 'com.acme.bundle2', ...);
 * </pre>
 * The result of this function may be assigned to the cacheBust config property so that any change
 * in any of the values of these headers for any of the specified bundles will result in a changed
 * cacheBust value.
 * <p>
 * An alternative name for the function may be specified by the <code>propName</code> init-param in
 * the plugin extension xml.
 */
public class BundleVersionsHash implements IExtensionInitializer, IConfigScopeModifier {
    private static final Logger log = Logger.getLogger(BundleVersionsHash.class.getName());

    private static final String DEFAULT_PROPNAME = "getBundleVersionsHash"; //$NON-NLS-1$

    protected List<IServiceRegistration> serviceRegs = new LinkedList<IServiceRegistration>();

    private String propName = null; // the name of the function (specified by plugin.xml)

    private Bundle contributingBundle = null;
    private IBundleResolver bundleResolver;

    /* (non-Javadoc)
     * @see com.ibm.jaggr.core.config.IConfigScopeModifier#modifyScope(com.ibm.jaggr.core.IAggregator, org.mozilla.javascript.Context, org.mozilla.javascript.Scriptable)
     */
    @Override
    public void modifyScope(IAggregator aggregator, Object scope) {
        final String sourceMethod = "prepareEnv"; //$NON-NLS-1$
        boolean isTraceLogging = log.isLoggable(Level.FINER);
        if (isTraceLogging) {
            log.entering(BundleVersionsHash.class.getName(), sourceMethod, new Object[] { aggregator, scope });
        }
        FunctionObject fn = new FunctionObject((Scriptable) scope, this);
        ScriptableObject.putProperty((Scriptable) scope, propName, fn);
        if (isTraceLogging) {
            log.exiting(BundleVersionsHash.class.getName(), sourceMethod);
        }
    }

    /* (non-Javadoc)
     * @see com.ibm.jaggr.core.IExtensionInitializer#initialize(com.ibm.jaggr.core.IAggregator, com.ibm.jaggr.core.IAggregatorExtension, com.ibm.jaggr.core.IExtensionInitializer.IExtensionRegistrar)
     */
    @Override
    public void initialize(IAggregator aggregator, IAggregatorExtension extension, IExtensionRegistrar registrar) {

        final String sourceMethod = "initialize"; //$NON-NLS-1$
        boolean isTraceLogging = log.isLoggable(Level.FINER);
        if (isTraceLogging) {
            log.entering(BundleVersionsHash.class.getName(), sourceMethod,
                    new Object[] { aggregator, extension, registrar });
        }
        // get the name of the function from the extension attributes
        propName = extension.getInitParams().getValue("propName"); //$NON-NLS-1$
        if (propName == null) {
            propName = DEFAULT_PROPNAME;
        }
        if (isTraceLogging) {
            log.finer("propName = " + propName); //$NON-NLS-1$
        }
        if (aggregator instanceof AggregatorImpl) { // can be an IAggregator mock when unit testing
            contributingBundle = ((AggregatorImpl) aggregator).getContributingBundle();
        }
        bundleResolver = BundleResolverFactory.getResolver(contributingBundle);

        if (isTraceLogging) {
            log.exiting(BundleVersionsHash.class.getName(), sourceMethod);
        }
    }

    /**
     * Returns the bundle headers for the specified bundle.
     *
     * @param bundleName
     *            the bundle name
     * @return the bundle headers for the bundle.
     * @throws NotFoundException if no matching bundle is found.
     */
    private Dictionary<?, ?> getBundleHeaders(String bundleName) throws NotFoundException {
        final String sourceMethod = "getBundleHeaders"; //$NON-NLS-1$
        boolean isTraceLogging = log.isLoggable(Level.FINER);
        if (isTraceLogging) {
            log.entering(BundleVersionsHash.class.getName(), sourceMethod, new Object[] { bundleName });
        }

        Bundle result = ".".equals(bundleName) ? contributingBundle : bundleResolver.getBundle(bundleName); //$NON-NLS-1$

        if (result == null) {
            throw new NotFoundException("Bundle " + bundleName + " not found."); //$NON-NLS-1$ //$NON-NLS-2$
        }
        if (isTraceLogging) {
            log.exiting(BundleVersionsHash.class.getName(), sourceMethod, result.getHeaders());
        }
        return result.getHeaders();
    }

    static private class FunctionObject extends org.mozilla.javascript.FunctionObject {
        private static final long serialVersionUID = 3107068841668570072L;

        static Method method;

        static final Set<String> defaultHeaders;

        private BundleVersionsHash instance;

        static {
            try {
                method = FunctionObject.class.getMethod("bundleVersionsHash", Context.class, Scriptable.class, //$NON-NLS-1$
                        Object[].class, Function.class);
            } catch (SecurityException e) {
                log.log(Level.SEVERE, e.getMessage(), e);
            } catch (NoSuchMethodException e) {
                log.log(Level.SEVERE, e.getMessage(), e);
            }
            Set<String> headers = new HashSet<String>();
            headers.add("Bundle-Version"); //$NON-NLS-1$
            defaultHeaders = Collections.unmodifiableSet(headers);
        }

        FunctionObject(Scriptable scope, BundleVersionsHash instance) {
            super("bundleVersionsHash", method, scope); //$NON-NLS-1$
            this.instance = instance;
        }

        public static Set<String> getHeaderNames(NativeArray nativeArray) {
            final String sourceMethod = "getHeaderNames"; //$NON-NLS-1$
            boolean isTraceLogging = log.isLoggable(Level.FINER);
            if (isTraceLogging) {
                log.entering(BundleVersionsHash.FunctionObject.class.getName(), sourceMethod,
                        new Object[] { nativeArray });
            }
            Set<String> result = new HashSet<String>();
            Object[] items = nativeArray.toArray(new Object[nativeArray.size()]);
            for (Object item : items) {
                if (!(item instanceof String)) {
                    throw new IllegalArgumentException(item.toString());
                }
                result.add((String) item);
            }
            if (isTraceLogging) {
                log.exiting(BundleVersionsHash.FunctionObject.class.getName(), sourceMethod, result);
            }
            return result;
        }

        @SuppressWarnings("unused")
        public static Object bundleVersionsHash(Context cx, Scriptable thisObj, Object[] args, Function funObj)
                throws NotFoundException {
            final String sourceMethod = "bundleVersionsHash"; //$NON-NLS-1$
            boolean isTraceLogging = log.isLoggable(Level.FINER);
            if (isTraceLogging) {
                log.entering(BundleVersionsHash.FunctionObject.class.getName(), sourceMethod,
                        new Object[] { cx, thisObj, args, funObj });
            }
            Object result = Context.getUndefinedValue();
            FunctionObject javaThis = (FunctionObject) funObj;
            StringBuffer sb = new StringBuffer();
            Set<String> headersToInclude = defaultHeaders;
            int i = 0;
            // args is an array of bundle names
            for (Object arg : args) {
                if (i++ == 0 && arg instanceof NativeArray) {
                    headersToInclude = getHeaderNames((NativeArray) arg);
                    NativeArray nativeArray = (NativeArray) arg;
                    Object[] array = nativeArray.toArray(new Object[nativeArray.size()]);
                } else if (arg instanceof String) {
                    Dictionary<?, ?> headers = javaThis.instance.getBundleHeaders(arg.toString());
                    for (String header : headersToInclude) {
                        Object obj = headers.get(header);
                        String value = obj.toString();
                        if (value != null) {
                            sb.append(sb.length() == 0 ? "" : ",").append(value); //$NON-NLS-1$ //$NON-NLS-2$
                        }
                        if (isTraceLogging) {
                            log.finer("bundle '" + arg.toString() + "': " + header + "=" + value); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
                        }
                    }
                } else {
                    throw new IllegalArgumentException("Invalid argument type:" + arg.toString()); //$NON-NLS-1$
                }
            }
            if (sb.length() > 0) {
                MessageDigest md = null;
                try {
                    md = MessageDigest.getInstance("MD5"); //$NON-NLS-1$
                    result = Base64.encodeBase64URLSafeString(md.digest(sb.toString().getBytes("UTF-8"))); //$NON-NLS-1$
                } catch (Exception e) {
                    if (log.isLoggable(Level.SEVERE)) {
                        log.log(Level.SEVERE, e.getMessage(), e);
                    }
                    throw new RuntimeException(e);
                }
            }
            if (isTraceLogging) {
                log.exiting(BundleVersionsHash.FunctionObject.class.getName(), sourceMethod, result);
            }
            return result;
        }
    }
}