/* * 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.sling.scripting.core.impl; import static org.apache.sling.scripting.api.BindingsValuesProvider.CONTEXT; import static org.apache.sling.scripting.api.BindingsValuesProvider.DEFAULT_CONTEXT; import java.util.ArrayList; import java.util.Collection; import java.util.Dictionary; import java.util.HashMap; import java.util.Hashtable; import java.util.List; import java.util.Map; import javax.script.ScriptEngineFactory; import org.apache.sling.commons.osgi.PropertiesUtil; import org.apache.sling.scripting.api.BindingsValuesProvider; import org.apache.sling.scripting.api.BindingsValuesProvidersByContext; import org.apache.sling.scripting.core.impl.helper.SlingScriptEngineManager; import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; import org.osgi.framework.ServiceReference; import org.osgi.service.component.ComponentContext; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.component.annotations.Reference; import org.osgi.service.component.annotations.ReferenceCardinality; import org.osgi.service.component.annotations.ReferencePolicy; import org.osgi.service.component.annotations.ReferencePolicyOption; import org.osgi.service.event.Event; import org.osgi.service.event.EventAdmin; import org.osgi.util.tracker.ServiceTracker; import org.osgi.util.tracker.ServiceTrackerCustomizer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** Our default {@link BindingsValuesProvidersByContext} implementation */ @Component( service = BindingsValuesProvidersByContext.class, property = { Constants.SERVICE_VENDOR + "=The Apache Software Foundation" } ) public class BindingsValuesProvidersByContextImpl implements BindingsValuesProvidersByContext, ServiceTrackerCustomizer { private final Map<String, ContextBvpCollector> customizers = new HashMap<>(); public static final String [] DEFAULT_CONTEXT_ARRAY = new String [] { DEFAULT_CONTEXT }; private static final String TOPIC_CREATED = "org/apache/sling/scripting/core/BindingsValuesProvider/CREATED"; private static final String TOPIC_MODIFIED = "org/apache/sling/scripting/core/BindingsValuesProvider/MODIFIED"; private static final String TOPIC_REMOVED = "org/apache/sling/scripting/core/BindingsValuesProvider/REMOVED"; private ServiceTracker bvpTracker; private ServiceTracker mapsTracker; private BundleContext bundleContext; private final Logger logger = LoggerFactory.getLogger(getClass()); private final List<ServiceReference> pendingRefs = new ArrayList<>(); @Reference private SlingScriptEngineManager scriptEngineManager; @Reference( policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.OPTIONAL ) private volatile EventAdmin eventAdmin; private abstract class ContextLoop { Object apply(ServiceReference ref) { final Object service = bundleContext.getService(ref); if(service != null) { for(String context : getContexts(ref)) { ContextBvpCollector c = customizers.get(context); if(c == null) { synchronized (BindingsValuesProvidersByContextImpl.this) { c = new ContextBvpCollector(bundleContext); customizers.put(context, c); } } applyInContext(c); } } return service; } protected abstract void applyInContext(ContextBvpCollector c); }; @Activate public void activate(ComponentContext ctx) { bundleContext = ctx.getBundleContext(); synchronized (pendingRefs) { for(ServiceReference ref : pendingRefs) { addingService(ref); } pendingRefs.clear(); } bvpTracker = new ServiceTracker(bundleContext, BindingsValuesProvider.class.getName(), this); bvpTracker.open(); // Map services can also be registered to provide bindings mapsTracker = new ServiceTracker(bundleContext, Map.class.getName(), this); mapsTracker.open(); } @Deactivate public void deactivate(ComponentContext ctx) { bvpTracker.close(); mapsTracker.close(); bundleContext = null; } @Override public Collection<BindingsValuesProvider> getBindingsValuesProviders( ScriptEngineFactory scriptEngineFactory, String context) { final List<BindingsValuesProvider> results = new ArrayList<>(); if(context == null) { context = DEFAULT_CONTEXT; } final ContextBvpCollector bvpc = customizers.get(context); if(bvpc == null) { logger.debug("no BindingsValuesProviderCustomizer available for context '{}'", context); return results; } results.addAll(bvpc.getGenericBindingsValuesProviders().values()); logger.debug("Generic BindingsValuesProviders added for engine {}: {}", scriptEngineFactory.getNames(), results); // we load the compatible language ones first so that the most specific // overrides these Map<Object, Object> factoryProps = scriptEngineManager.getProperties(scriptEngineFactory); if (factoryProps != null) { String[] compatibleLangs = PropertiesUtil.toStringArray(factoryProps.get("compatible.javax.script.name"), new String[0]); for (final String name : compatibleLangs) { final Map<ServiceReference, BindingsValuesProvider> langProviders = bvpc.getLangBindingsValuesProviders().get(name); if (langProviders != null) { results.addAll(langProviders.values()); } } logger.debug("Compatible BindingsValuesProviders added for engine {}: {}", scriptEngineFactory.getNames(), results); } for (final String name : scriptEngineFactory.getNames()) { final Map<ServiceReference, BindingsValuesProvider> langProviders = bvpc.getLangBindingsValuesProviders().get(name); if (langProviders != null) { results.addAll(langProviders.values()); } } logger.debug("All BindingsValuesProviders added for engine {}: {}", scriptEngineFactory.getNames(), results); return results; } private String [] getContexts(ServiceReference reference) { return PropertiesUtil.toStringArray(reference.getProperty(CONTEXT), new String[] { DEFAULT_CONTEXT }); } private Event newEvent(final String topic, final ServiceReference reference) { Dictionary<String, Object> props = new Hashtable<>(); props.put("service.id", reference.getProperty(Constants.SERVICE_ID)); return new Event(topic, props); } @Override public Object addingService(final ServiceReference reference) { if(bundleContext == null) { synchronized (pendingRefs) { pendingRefs.add(reference); } return null; } return new ContextLoop() { @Override protected void applyInContext(ContextBvpCollector c) { c.addingService(reference); if (eventAdmin != null) { eventAdmin.postEvent(newEvent(TOPIC_CREATED, reference)); } } }.apply(reference); } @Override public void modifiedService(final ServiceReference reference, final Object service) { new ContextLoop() { @Override protected void applyInContext(ContextBvpCollector c) { c.modifiedService(reference); if (eventAdmin != null) { eventAdmin.postEvent(newEvent(TOPIC_MODIFIED, reference)); } } }.apply(reference); } @Override public void removedService(final ServiceReference reference, final Object service) { if(bundleContext == null) { synchronized (pendingRefs) { pendingRefs.remove(reference); } return; } new ContextLoop() { @Override protected void applyInContext(ContextBvpCollector c) { c.removedService(reference); if (eventAdmin != null) { eventAdmin.postEvent(newEvent(TOPIC_REMOVED, reference)); } } }.apply(reference); } }