/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.apache.shindig.gadgets; import org.apache.shindig.gadgets.http.HttpFetcher; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.inject.Inject; import com.google.inject.Singleton; import com.google.inject.name.Named; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Logger; /** * Maintains a registry of all {@code GadgetFeature} types supported by * a given Gadget Server installation. * * To register a feature: * GadgetFeatureRegistry registry = // get your global registry * registry.register("my-feature", null, new MyFeatureFactory()); */ @Singleton public class GadgetFeatureRegistry { private final Map<String, GadgetFeature> features; private final Map<String, GadgetFeature> core; // Caches the transitive dependencies to enable faster lookups. final Map<Set<String>, Collection<GadgetFeature>> cache = Maps.newConcurrentHashMap(); private boolean graphComplete = false; private final static Logger logger = Logger.getLogger("org.apache.shindig.gadgets"); /** * Creates a new feature registry and loads the specified features. * * @param httpFetcher * @param featureFiles * @throws GadgetException */ @Inject public GadgetFeatureRegistry(@Named("shindig.features.default") String featureFiles, HttpFetcher httpFetcher) throws GadgetException { features = Maps.newHashMap(); core = Maps.newHashMap(); if (featureFiles != null) { JsFeatureLoader loader = new JsFeatureLoader(httpFetcher); loader.loadFeatures(featureFiles, this); } } /** * Register a {@code GadgetFeature}. * * @param feature Class implementing the feature. */ public void register(GadgetFeature feature) { if (graphComplete) { throw new IllegalStateException("register should never be " + "invoked after calling getLibraries"); } logger.info("Registering feature: " + feature.getName()); if (isCore(feature)) { core.put(feature.getName(), feature); for (GadgetFeature feat : features.values()) { feat.addDependency(feature.getName()); } } else { feature.addDependencies(core.keySet()); } features.put(feature.getName(), feature); } /** * @return True if the entry is "core" (a dependency of all other features) */ private boolean isCore(GadgetFeature feature) { return feature.getName().startsWith("core"); } /** * @return All registered features. */ public Collection<GadgetFeature> getAllFeatures() { return Collections.unmodifiableCollection(features.values()); } /** * @return All {@code GadgetFeature} objects necessary for {@code needed} in * graph-dependent order. */ public Collection<GadgetFeature> getFeatures(Collection<String> needed) { return getFeatures(needed, null); } /** * @param needed All features requested by the gadget. * @param unsupported Populated with any unsupported features. * @return All {@code GadgetFeature} objects necessary for {@code needed} in * graph-dependent order. */ public Collection<GadgetFeature> getFeatures(Collection<String> needed, Collection<String> unsupported) { graphComplete = true; Set<String> neededSet; if (needed.isEmpty()) { neededSet = core.keySet(); } else { neededSet = ImmutableSet.copyOf(needed); } // We use the cache only for situations where all needed are available. // if any are missing, the result won't be cached. Collection<GadgetFeature> libCache = cache.get(neededSet); if (libCache != null) { return libCache; } List<GadgetFeature> ret = Lists.newLinkedList(); populateDependencies(neededSet, ret); // Fill in anything that was optional but missing. These won't be cached. if (unsupported != null) { for (String feature : neededSet) { if (!features.containsKey(feature)) { unsupported.add(feature); } } } if (unsupported == null || unsupported.isEmpty()) { cache.put(neededSet, Collections.unmodifiableList(ret)); logger.info("Added to cache. Size is now: " + cache.size()); } return ret; } /** * Recursively populates {@code libraries} with libraries from dependent * features. This ensures that features will always be loaded in the order * that they are declared. */ private void populateDependencies(Collection<String> needed, List<GadgetFeature> deps) { for (String feature : needed) { GadgetFeature feat = features.get(feature); if (feat != null && !deps.contains(feat)) { populateDependencies(feat.getDependencies(), deps); deps.add(feat); } } } }