/**
* Copyright OPS4J
*
* 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.ops4j.pax.wicket.internal.extender;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.ops4j.pax.wicket.api.Constants;
import org.ops4j.pax.wicket.api.WebApplicationFactory;
import org.ops4j.pax.wicket.internal.BundleDelegatingClassResolver;
import org.ops4j.pax.wicket.internal.BundleDelegatingPageMounter;
import org.ops4j.pax.wicket.internal.injection.BundleDelegatingComponentInstanciationListener;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleReference;
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.util.tracker.BundleTracker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Right now it listens on all pax-wicket applications. In addition it is "feeded" by a bundleListeners with all bundles
* implementing org.apache.wicket.
*
* If a service is added a new BundleDelegatingVersion of the classloaders, injection handlers and mount point listeners
* is added to the service reference. Initally all currently registered bundles are checked then if they should be added
* into the specific lifecycle for a specific application.
*
* If an application get updated the check if bundles are still valid for this package are repeated.
*
* Every time a bundle is added it is evaluated to which BundleDelegatingServices this bundle should be added (and is
* added to the matching services).
*
* Everytime a bundle is removed it is simply removed from all applications from all services.
*
* @author nmw
* @version $Id: $Id
*/
@Component
public class BundleDelegatingExtensionTracker extends PaxWicketBundleListener {
private static final Logger LOGGER = LoggerFactory.getLogger(BundleDelegatingExtensionTracker.class);
private final Map<String, ExtendedBundle> relvantBundles = new HashMap<String, ExtendedBundle>();
private final Map<WebApplicationFactory<?>, BundleDelegatingClassResolver> classResolvers =
new HashMap<WebApplicationFactory<?>, BundleDelegatingClassResolver>();
private final Map<WebApplicationFactory<?>, BundleDelegatingComponentInstanciationListener> componentInstanciationListener =
new HashMap<WebApplicationFactory<?>, BundleDelegatingComponentInstanciationListener>();
private final Map<WebApplicationFactory<?>, BundleDelegatingPageMounter> pageMounter =
new HashMap<WebApplicationFactory<?>, BundleDelegatingPageMounter>();
private BundleTracker<ExtendedBundle> bundleExtensionTracker;
/** {@inheritDoc} */
@Override
@Activate
public void activate(BundleContext bundleContext) {
super.activate(bundleContext);
bundleExtensionTracker = new BundleTracker<ExtendedBundle>(bundleContext, Bundle.ACTIVE, this);
bundleExtensionTracker.open();
}
/**
* <p>deactivate.</p>
*
* @since 3.0.5
*/
@Deactivate
public void deactivate() {
bundleExtensionTracker.close();
}
/**
* <p>addWebApplicationFactory.</p>
*
* @param webApplicationFactory a {@link org.ops4j.pax.wicket.api.WebApplicationFactory} object.
* @param properties a {@link java.util.Map} object.
* @since 3.0.5
*/
@Reference(service = WebApplicationFactory.class, unbind = "removeWebApplicationFactory",
updated = "modifiedWebApplicationFactory",
cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
public void addWebApplicationFactory(WebApplicationFactory<?> webApplicationFactory, Map<String, ?> properties) {
synchronized (this) {
addServicesForServiceReference(webApplicationFactory, properties);
reevaluateAllBundles(webApplicationFactory);
}
}
/**
* <p>modifiedWebApplicationFactory.</p>
*
* @param webApplicationFactory a {@link org.ops4j.pax.wicket.api.WebApplicationFactory} object.
* @param properties a {@link java.util.Map} object.
* @since 3.0.5
*/
public void
modifiedWebApplicationFactory(WebApplicationFactory<?> webApplicationFactory, Map<String, ?> properties) {
// TODO check if this is really needed or if we are fine with the normal remove/add provided by DS...
synchronized (this) {
removeServicesForServiceReference(webApplicationFactory);
addServicesForServiceReference(webApplicationFactory, properties);
reevaluateAllBundles(webApplicationFactory);
}
}
/**
* <p>removeWebApplicationFactory.</p>
*
* @param webApplicationFactory a {@link org.ops4j.pax.wicket.api.WebApplicationFactory} object.
* @since 3.0.5
*/
public void removeWebApplicationFactory(WebApplicationFactory<?> webApplicationFactory) {
synchronized (this) {
removeServicesForServiceReference(webApplicationFactory);
}
}
private void addServicesForServiceReference(WebApplicationFactory<?> webApplicationFactory,
Map<String, ?> properties) {
Object applicationName = properties.get(Constants.APPLICATION_NAME);
if (applicationName == null) {
throw new IllegalArgumentException("The service must provide a '" + Constants.APPLICATION_NAME
+ "' property");
}
// fetch it via the classloader because of the dynamic nature of service adding might happen before component is
// activated. We should change this later to use a more generic aproach eg. extracting this to different
// (independent) components
BundleContext paxWicketBundleContext =
((BundleReference) BundleDelegatingClassResolver.class.getClassLoader()).getBundle().getBundleContext();
classResolvers.put(webApplicationFactory, new BundleDelegatingClassResolver(paxWicketBundleContext,
applicationName.toString()));
classResolvers.get(webApplicationFactory).start();
componentInstanciationListener.put(webApplicationFactory, new BundleDelegatingComponentInstanciationListener(
paxWicketBundleContext, applicationName.toString()));
componentInstanciationListener.get(webApplicationFactory).start();
pageMounter.put(webApplicationFactory, new BundleDelegatingPageMounter(applicationName.toString(),
paxWicketBundleContext));
pageMounter.get(webApplicationFactory).start();
}
private void removeServicesForServiceReference(WebApplicationFactory<?> webApplicationFactory) {
classResolvers.get(webApplicationFactory).stop();
classResolvers.remove(webApplicationFactory);
componentInstanciationListener.get(webApplicationFactory).stop();
componentInstanciationListener.remove(webApplicationFactory);
pageMounter.get(webApplicationFactory).stop();
pageMounter.remove(webApplicationFactory);
}
private void reevaluateAllBundles(WebApplicationFactory<?> webApplicationFactory) {
Collection<ExtendedBundle> bundles = relvantBundles.values();
for (ExtendedBundle bundle : bundles) {
addBundleToServicesReference(bundle, webApplicationFactory);
}
}
/** {@inheritDoc} */
@Override
public void addRelevantBundle(ExtendedBundle bundle) {
synchronized (this) {
LOGGER.debug("this bundle is relevant {}",bundle.getID());
relvantBundles.put(bundle.getID(), bundle);
for (WebApplicationFactory<?> serviceReference : classResolvers.keySet()) {
addBundleToServicesReference(bundle, serviceReference);
}
}
}
private void addBundleToServicesReference(ExtendedBundle bundle,
WebApplicationFactory<?> webApplicationFactory) {
try {
classResolvers.get(webApplicationFactory).addBundle(bundle);
} catch (Throwable e) {
LOGGER.warn("A specific reference could not be added to the classResolvers; might not be too bad", e);
}
try {
componentInstanciationListener.get(webApplicationFactory).addBundle(bundle);
} catch (Throwable e) {
LOGGER.warn(
"A specific reference could not be added to the componentInstanciationListener; might not be too bad",
e);
}
try {
pageMounter.get(webApplicationFactory).addBundle(bundle);
} catch (Throwable e) {
LOGGER.warn("A specific reference could not be added to pageMounter; might not be too bad", e);
}
}
/** {@inheritDoc} */
@Override
public void removeRelevantBundle(ExtendedBundle bundle) {
synchronized (this) {
relvantBundles.remove(bundle.getID());
removeBundleFromAllServices(bundle);
}
}
private void removeBundleFromAllServices(ExtendedBundle bundle) {
for (WebApplicationFactory<?> reference : classResolvers.keySet()) {
classResolvers.get(reference).removeBundle(bundle);
componentInstanciationListener.get(reference).removeBundle(bundle);
pageMounter.get(reference).removeBundle(bundle);
}
}
}