Return-Path: Delivered-To: apmail-incubator-aries-commits-archive@minotaur.apache.org Received: (qmail 6214 invoked from network); 5 Aug 2010 11:15:23 -0000 Received: from unknown (HELO mail.apache.org) (140.211.11.3) by 140.211.11.9 with SMTP; 5 Aug 2010 11:15:23 -0000 Received: (qmail 30012 invoked by uid 500); 5 Aug 2010 11:15:22 -0000 Delivered-To: apmail-incubator-aries-commits-archive@incubator.apache.org Received: (qmail 29906 invoked by uid 500); 5 Aug 2010 11:15:20 -0000 Mailing-List: contact aries-commits-help@incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: aries-dev@incubator.apache.org Delivered-To: mailing list aries-commits@incubator.apache.org Received: (qmail 29898 invoked by uid 99); 5 Aug 2010 11:15:19 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 05 Aug 2010 11:15:19 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=10.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 05 Aug 2010 11:15:13 +0000 Received: by eris.apache.org (Postfix, from userid 65534) id 508A623888D7; Thu, 5 Aug 2010 11:13:56 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r982543 [1/2] - in /incubator/aries/trunk/jpa: ./ jpa-container-context/ jpa-container-context/src/main/java/org/apache/aries/jpa/container/context/impl/ jpa-container-context/src/main/java/org/apache/aries/jpa/container/context/transaction... Date: Thu, 05 Aug 2010 11:13:56 -0000 To: aries-commits@incubator.apache.org From: timothyjward@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20100805111356.508A623888D7@eris.apache.org> Author: timothyjward Date: Thu Aug 5 11:13:55 2010 New Revision: 982543 URL: http://svn.apache.org/viewvc?rev=982543&view=rev Log: ARIES-374 - JPA Persistence context quiesce support Added: incubator/aries/trunk/jpa/jpa-container-context/src/main/java/org/apache/aries/jpa/container/context/impl/QuiesceParticipantImpl.java incubator/aries/trunk/jpa/jpa-container-context/src/main/java/org/apache/aries/jpa/container/context/transaction/impl/DestroyCallback.java incubator/aries/trunk/jpa/jpa-container-context/src/test/java/org/apache/aries/jpa/container/context/transaction/impl/TranSyncRegistryMock.java incubator/aries/trunk/jpa/jpa-container-itest/src/test/java/org/apache/aries/jpa/quiesce/ incubator/aries/trunk/jpa/jpa-container-itest/src/test/java/org/apache/aries/jpa/quiesce/itest/ incubator/aries/trunk/jpa/jpa-container-itest/src/test/java/org/apache/aries/jpa/quiesce/itest/QuiesceJPAContextTest.java incubator/aries/trunk/jpa/jpa-container-testbundle/src/main/resources/META-INF/ incubator/aries/trunk/jpa/jpa-container-testbundle/src/main/resources/META-INF/persistence.xml Removed: incubator/aries/trunk/jpa/jpa-container-context/src/main/resources/OSGI-INF/ incubator/aries/trunk/jpa/jpa-container-testbundle/src/main/resources/OSGI-INF/ Modified: incubator/aries/trunk/jpa/jpa-container-context/pom.xml incubator/aries/trunk/jpa/jpa-container-context/src/main/java/org/apache/aries/jpa/container/context/impl/GlobalPersistenceManager.java incubator/aries/trunk/jpa/jpa-container-context/src/main/java/org/apache/aries/jpa/container/context/impl/ManagedPersistenceContextFactory.java incubator/aries/trunk/jpa/jpa-container-context/src/main/java/org/apache/aries/jpa/container/context/impl/PersistenceContextManager.java incubator/aries/trunk/jpa/jpa-container-context/src/main/java/org/apache/aries/jpa/container/context/transaction/impl/JTAEntityManager.java incubator/aries/trunk/jpa/jpa-container-context/src/main/java/org/apache/aries/jpa/container/context/transaction/impl/JTAPersistenceContextRegistry.java incubator/aries/trunk/jpa/jpa-container-context/src/test/java/org/apache/aries/jpa/container/context/impl/GlobalPersistenceManagerTest.java incubator/aries/trunk/jpa/jpa-container-context/src/test/java/org/apache/aries/jpa/container/context/impl/PersistenceContextManagerTest.java incubator/aries/trunk/jpa/jpa-container-context/src/test/java/org/apache/aries/jpa/container/context/transaction/impl/JTAPersistenceContextRegistryTest.java incubator/aries/trunk/jpa/jpa-container-itest/pom.xml incubator/aries/trunk/jpa/jpa-container-testbundle/pom.xml incubator/aries/trunk/jpa/pom.xml Modified: incubator/aries/trunk/jpa/jpa-container-context/pom.xml URL: http://svn.apache.org/viewvc/incubator/aries/trunk/jpa/jpa-container-context/pom.xml?rev=982543&r1=982542&r2=982543&view=diff ============================================================================== --- incubator/aries/trunk/jpa/jpa-container-context/pom.xml (original) +++ incubator/aries/trunk/jpa/jpa-container-context/pom.xml Thu Aug 5 11:13:55 2010 @@ -28,6 +28,13 @@ Aries JPA Container Managed Contexts bundle + + + org.apache.aries.jpa.container.context.impl.GlobalPersistenceManager + + + + org.osgi @@ -60,6 +67,11 @@ provided + org.apache.aries.quiesce + org.apache.aries.quiesce.api + provided + + org.slf4j slf4j-api @@ -94,6 +106,8 @@ javax.persistence.criteria;version="[1.1.0,2.1.0)";resolution:=optional, javax.persistence.metamodel;version="[1.1.0,2.1.0)";resolution:=optional, org.apache.aries.jpa.container.context;version="[0.1.0,1.1.0)", + org.apache.aries.quiesce.manager;version="[0.2,0.3)";resolution:=optional, + org.apache.aries.quiesce.participant;version="[0.2,0.3)";resolution:=optional, * <_versionpolicy>[$(version;==;$(@)),$(version;+;$(@))) Modified: incubator/aries/trunk/jpa/jpa-container-context/src/main/java/org/apache/aries/jpa/container/context/impl/GlobalPersistenceManager.java URL: http://svn.apache.org/viewvc/incubator/aries/trunk/jpa/jpa-container-context/src/main/java/org/apache/aries/jpa/container/context/impl/GlobalPersistenceManager.java?rev=982543&r1=982542&r2=982543&view=diff ============================================================================== --- incubator/aries/trunk/jpa/jpa-container-context/src/main/java/org/apache/aries/jpa/container/context/impl/GlobalPersistenceManager.java (original) +++ incubator/aries/trunk/jpa/jpa-container-context/src/main/java/org/apache/aries/jpa/container/context/impl/GlobalPersistenceManager.java Thu Aug 5 11:13:55 2010 @@ -18,15 +18,22 @@ */ package org.apache.aries.jpa.container.context.impl; +import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.Map.Entry; import org.apache.aries.jpa.container.context.PersistenceContextProvider; +import org.apache.aries.jpa.container.context.transaction.impl.DestroyCallback; import org.apache.aries.jpa.container.context.transaction.impl.JTAPersistenceContextRegistry; import org.osgi.framework.Bundle; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; import org.osgi.framework.BundleEvent; +import org.osgi.framework.ServiceRegistration; import org.osgi.framework.SynchronousBundleListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,7 +41,10 @@ import org.slf4j.LoggerFactory; /** * Class that coordinates PersistenceContextManagers across multiple (nested) OSGi frameworks. */ -public class GlobalPersistenceManager implements PersistenceContextProvider, SynchronousBundleListener { +public class GlobalPersistenceManager implements PersistenceContextProvider, SynchronousBundleListener, BundleActivator { + /** The QuiesceParticipant implementation class name */ + private static final String QUIESCE_PARTICIPANT_CLASS = "org.apache.aries.quiesce.participant.QuiesceParticipant"; + /** Logger */ private static final Logger _logger = LoggerFactory.getLogger("org.apache.aries.jpa.container.context"); @@ -54,12 +64,14 @@ public class GlobalPersistenceManager im */ private Map> persistenceContexts = new HashMap>(); - - public void setRegistry(JTAPersistenceContextRegistry registry) { - this.registry = registry; - } - + /** The registration for the quiesce participant */ + private ServiceRegistration quiesceReg; + /** The registration for the persistence context manager */ + private ServiceRegistration pcpReg; + /** Our bundle */ + private Bundle bundle; + public void registerContext(String unitName, Bundle client, HashMap properties) { if (_logger.isDebugEnabled()) { _logger.debug("Registering bundle {} as a client of persistence unit {} with properties {}.", @@ -167,4 +179,107 @@ public class GlobalPersistenceManager im } } + /** + * Quiesce the supplied bundle + * @param bundleToQuiesce + * @param callback + */ + public void quiesceBundle(Bundle bundleToQuiesce, final DestroyCallback callback) { + //If this is our bundle then get all the managers to clean up everything + if(bundleToQuiesce == bundle) { + unregister(pcpReg); + pcpReg = null; + final Collection> mgrs = new ArrayList>(); + synchronized (this) { + mgrs.addAll(managers.entrySet()); + } + + if(managers.isEmpty()) + callback.callback(); + else { + final GlobalPersistenceManager gpm = this; + //A special callback to say we're done when everyone has replied! + DestroyCallback destroy = new DestroyCallback() { + int count = mgrs.size(); + public void callback() { + count--; + if(count == 0) { + for(Entry entry : mgrs){ + BundleContext ctx = entry.getKey().getBundleContext(); + if(ctx != null) { + ctx.removeBundleListener(gpm); + } + } + callback.callback(); + } + } + }; + for (Entry entry : mgrs) + entry.getValue().quiesceAllUnits(destroy); + } + } else { + //Just quiesce the one bundle + Bundle frameworkBundle = bundleToQuiesce.getBundleContext().getBundle(0); + PersistenceContextManager mgr = managers.get(frameworkBundle); + + //If there is no manager for this framework, then we aren't managing the bundle + //and can stop now + if(mgr == null) { + callback.callback(); + } else { + mgr.quiesceUnits(bundleToQuiesce, callback); + } + } + } + + public void start(BundleContext context) throws Exception { + + bundle = context.getBundle(); + registry = new JTAPersistenceContextRegistry(context); + + //Register our service + pcpReg = context.registerService(PersistenceContextProvider.class.getName(), this, null); + + try{ + context.getBundle().loadClass(QUIESCE_PARTICIPANT_CLASS); + //Class was loaded, register + + quiesceReg = context.registerService(QUIESCE_PARTICIPANT_CLASS, + new QuiesceParticipantImpl(this), null); + } catch (ClassNotFoundException e) { + _logger.info("No quiesce support is available, so persistence contexts will not participate in quiesce operations", e); + } + } + + public void stop(BundleContext context) throws Exception { + //Clean up + unregister(pcpReg); + unregister(quiesceReg); + Collection mgrs = new ArrayList(); + synchronized (persistenceContexts) { + mgrs.addAll(managers.values()); + } + + for(PersistenceContextManager mgr : mgrs) + mgr.close(); + + registry.close(); + } + + /** + * Clean up a registration without throwing an exception + * @param reg + */ + private void unregister(ServiceRegistration reg) { + if(reg != null) + try { + reg.unregister(); + } catch (IllegalStateException ise) { + //we don't care + } + } + + public Bundle getBundle() { + return bundle; + } } Modified: incubator/aries/trunk/jpa/jpa-container-context/src/main/java/org/apache/aries/jpa/container/context/impl/ManagedPersistenceContextFactory.java URL: http://svn.apache.org/viewvc/incubator/aries/trunk/jpa/jpa-container-context/src/main/java/org/apache/aries/jpa/container/context/impl/ManagedPersistenceContextFactory.java?rev=982543&r1=982542&r2=982543&view=diff ============================================================================== --- incubator/aries/trunk/jpa/jpa-container-context/src/main/java/org/apache/aries/jpa/container/context/impl/ManagedPersistenceContextFactory.java (original) +++ incubator/aries/trunk/jpa/jpa-container-context/src/main/java/org/apache/aries/jpa/container/context/impl/ManagedPersistenceContextFactory.java Thu Aug 5 11:13:55 2010 @@ -20,7 +20,9 @@ package org.apache.aries.jpa.container.c import java.util.HashMap; import java.util.Map; -import java.util.Properties; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; import javax.persistence.Cache; import javax.persistence.EntityManager; @@ -31,6 +33,8 @@ import javax.persistence.criteria.Criter import javax.persistence.metamodel.Metamodel; import org.apache.aries.jpa.container.context.PersistenceContextProvider; +import org.apache.aries.jpa.container.context.impl.PersistenceContextManager.QuiesceTidyUp; +import org.apache.aries.jpa.container.context.transaction.impl.DestroyCallback; import org.apache.aries.jpa.container.context.transaction.impl.JTAEntityManager; import org.apache.aries.jpa.container.context.transaction.impl.JTAPersistenceContextRegistry; import org.osgi.framework.ServiceReference; @@ -41,8 +45,10 @@ import org.slf4j.LoggerFactory; * This is registered in the Service registry to be looked up by blueprint. * The EntityManagerFactory interface is used to ensure a shared class space * with the client. Only the createEntityManager() method is supported. + * + * Also this class receives a callback on cleanup */ -public class ManagedPersistenceContextFactory implements EntityManagerFactory { +public class ManagedPersistenceContextFactory implements EntityManagerFactory, DestroyCallback { /** Logger */ private static final Logger _logger = LoggerFactory.getLogger("org.apache.aries.jpa.container.context"); @@ -50,10 +56,15 @@ public class ManagedPersistenceContextFa private final Map properties; private final JTAPersistenceContextRegistry registry; private final PersistenceContextType type; - - public ManagedPersistenceContextFactory(ServiceReference unit, + private final AtomicBoolean quiesce = new AtomicBoolean(false); + private final AtomicLong activeCount = new AtomicLong(0); + private final String unitName; + + private final AtomicReference tidyUp = new AtomicReference(); + + public ManagedPersistenceContextFactory(String name, ServiceReference unit, Map props, JTAPersistenceContextRegistry contextRegistry) { - + unitName = name; emf = unit; //Take a copy of the Map so that we don't modify the original properties = new HashMap(props); @@ -70,7 +81,7 @@ public class ManagedPersistenceContextFa EntityManagerFactory factory = (EntityManagerFactory) emf.getBundle().getBundleContext().getService(emf); if(type == PersistenceContextType.TRANSACTION || type == null) - return new JTAEntityManager(factory, properties, registry); + return new JTAEntityManager(factory, properties, registry, activeCount, this); else { _logger.error("There is currently no support for extended scope EntityManagers"); return null; @@ -110,4 +121,25 @@ public class ManagedPersistenceContextFa throw new UnsupportedOperationException(); } + /** + * Register an async Quiesce operation with this peristence context + * @param tidyUp + */ + public void quiesce(QuiesceTidyUp tidyUp) { + this.tidyUp.set(tidyUp); + quiesce.set(true); + if(activeCount.get() == 0) { + tidyUp.unitQuiesced(unitName); + } + } + + /** + * Quiesce this unit after the last context is destroyed + */ + public void callback() { + if(quiesce.get() && activeCount.get() == 0) { + tidyUp.get().unitQuiesced(unitName); + } + } + } Modified: incubator/aries/trunk/jpa/jpa-container-context/src/main/java/org/apache/aries/jpa/container/context/impl/PersistenceContextManager.java URL: http://svn.apache.org/viewvc/incubator/aries/trunk/jpa/jpa-container-context/src/main/java/org/apache/aries/jpa/container/context/impl/PersistenceContextManager.java?rev=982543&r1=982542&r2=982543&view=diff ============================================================================== --- incubator/aries/trunk/jpa/jpa-container-context/src/main/java/org/apache/aries/jpa/container/context/impl/PersistenceContextManager.java (original) +++ incubator/aries/trunk/jpa/jpa-container-context/src/main/java/org/apache/aries/jpa/container/context/impl/PersistenceContextManager.java Thu Aug 5 11:13:55 2010 @@ -18,17 +18,23 @@ */ package org.apache.aries.jpa.container.context.impl; +import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Set; +import java.util.Map.Entry; import javax.persistence.EntityManagerFactory; import javax.persistence.PersistenceContextType; import org.apache.aries.jpa.container.PersistenceUnitConstants; import org.apache.aries.jpa.container.context.PersistenceContextProvider; +import org.apache.aries.jpa.container.context.transaction.impl.DestroyCallback; import org.apache.aries.jpa.container.context.transaction.impl.JTAPersistenceContextRegistry; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; @@ -268,7 +274,7 @@ public class PersistenceContextManager e } //Create the service factory - entityManagerServiceFactory = new ManagedPersistenceContextFactory(unit, props, persistenceContextRegistry); + entityManagerServiceFactory = new ManagedPersistenceContextFactory(name, unit, props, persistenceContextRegistry); } //Always register from outside a synchronized @@ -397,4 +403,117 @@ public class PersistenceContextManager e public void open() { super.open(true); } + + /** + * Call this method to quiesce a given bundle + * @param bundleToQuiesce The bundle to quiesce + * @param callback A callback indicating that we have finished quiescing + */ + public void quiesceUnits(Bundle bundleToQuiesce, DestroyCallback callback) { + + //Find the units supplied by this bundle + List units = new ArrayList(); + synchronized (this) { + Iterator> it = persistenceUnits.entrySet().iterator(); + while(it.hasNext()) { + Entry entry = it.next(); + ServiceReference value = entry.getValue(); + //If the quiescing bundle supplies the unit then unget the unit and remove it + if(value.getBundle().equals(bundleToQuiesce)) { + units.add(entry.getKey()); + context.ungetService(entry.getValue()); + //remove it to prevent other units that start using it + it.remove(); + } + } + } + //If there are no units then our work is done! + if(units.isEmpty()) { + callback.callback(); + } else { + //Find the ManagedFactories for the persistence unit + List factoriesToQuiesce = new ArrayList(); + synchronized (this) { + for(String name : units) { + ServiceRegistration reg = entityManagerRegistrations.get(name); + if(reg != null) { + ManagedPersistenceContextFactory fact = (ManagedPersistenceContextFactory) bundleToQuiesce.getBundleContext().getService(reg.getReference()); + if(fact != null) + factoriesToQuiesce.add(fact); + } + } + } + //If no factories are registered then we're done + if(factoriesToQuiesce.isEmpty()) { + callback.callback(); + } else { + //Create a new Tidy up helper and tell the factories to tidy up + QuiesceTidyUp tidyUp = new QuiesceTidyUp(units, callback); + + for(ManagedPersistenceContextFactory fact : factoriesToQuiesce) { + fact.quiesce(tidyUp); + } + } + } + } + + /** + * Quiesce all the persistence units managed by this {@link PersistenceContextManager} + * @param callback + */ + public void quiesceAllUnits(final DestroyCallback callback) { + + Collection names = new ArrayList(); + Collection factoriesToQuiesce = new ArrayList(); + + //Get all the resources + synchronized (this) { + names.addAll(entityManagerRegistrations.keySet()); + factoriesToQuiesce.addAll(entityManagerRegistrations.values()); + } + //If there are no names or factories then we're done + if(names.isEmpty() || factoriesToQuiesce.isEmpty()) { + callback.callback(); + } else { + + //Register an async tidy up + QuiesceTidyUp tidyUp = new QuiesceTidyUp(names, callback); + + for(ServiceRegistration reg : factoriesToQuiesce) { + ManagedPersistenceContextFactory fact = (ManagedPersistenceContextFactory) reg.getReference().getBundle().getBundleContext().getService(reg.getReference()); + fact.quiesce(tidyUp); + } + } + } + + /** + * An asynchronous tidy up operation + */ + class QuiesceTidyUp { + //The units being tidied up + private final Set units; + //The callback for when we're done + private final DestroyCallback dc; + + public QuiesceTidyUp(Collection units, + DestroyCallback callback) { + this.units = new HashSet(units); + dc = callback; + } + + public void unitQuiesced(String name) { + unregisterEM(name); + boolean winner; + synchronized(this) { + winner = units.remove(name) && units.isEmpty(); + } + + if(winner) { + dc.callback(); + } + } + } + + + } Added: incubator/aries/trunk/jpa/jpa-container-context/src/main/java/org/apache/aries/jpa/container/context/impl/QuiesceParticipantImpl.java URL: http://svn.apache.org/viewvc/incubator/aries/trunk/jpa/jpa-container-context/src/main/java/org/apache/aries/jpa/container/context/impl/QuiesceParticipantImpl.java?rev=982543&view=auto ============================================================================== --- incubator/aries/trunk/jpa/jpa-container-context/src/main/java/org/apache/aries/jpa/container/context/impl/QuiesceParticipantImpl.java (added) +++ incubator/aries/trunk/jpa/jpa-container-context/src/main/java/org/apache/aries/jpa/container/context/impl/QuiesceParticipantImpl.java Thu Aug 5 11:13:55 2010 @@ -0,0 +1,137 @@ +/** + * 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.aries.jpa.container.context.impl; + +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import org.apache.aries.jpa.container.context.transaction.impl.DestroyCallback; +import org.apache.aries.quiesce.manager.QuiesceCallback; +import org.apache.aries.quiesce.participant.QuiesceParticipant; +import org.osgi.framework.Bundle; + +/** + * This class provides Quiesce Participant support for JPA managed contexts. It is the only + * class in this bundle that depends on the Quiesce API to make sure that the bundle can + * optionally depend on the API. If no Quiesce API is available then this class will not be + * loaded and no Quiesce support will be available. + */ +public class QuiesceParticipantImpl implements QuiesceParticipant, DestroyCallback { + + /** + * A wrapper to protect our internals from the Quiesce API so that we can make it + * an optional dependency + */ + private static final class QuiesceDelegatingCallback implements DestroyCallback { + + /** The callback to delegate to */ + private final QuiesceCallback callback; + + /** The single bundle being quiesced by this DestroyCallback */ + private final Bundle toQuiesce; + + public QuiesceDelegatingCallback(QuiesceCallback cbk, Bundle b) { + callback = cbk; + toQuiesce = b; + } + + public void callback() { + callback.bundleQuiesced(toQuiesce); + } + + } + + /** + * A runnable Quiesce operation for a single bundle + */ + private static final class QuiesceBundle implements Runnable { + + /** The callback when we're done */ + private final DestroyCallback callback; + /** The bundle being quiesced */ + private final Bundle bundleToQuiesce; + /** The GlobalPersistenceManager instance */ + private final GlobalPersistenceManager mgr; + + public QuiesceBundle(QuiesceCallback callback, Bundle bundleToQuiesce, + GlobalPersistenceManager mgr) { + super(); + this.callback = new QuiesceDelegatingCallback(callback, bundleToQuiesce); + this.bundleToQuiesce = bundleToQuiesce; + this.mgr = mgr; + } + + public void run() { + mgr.quiesceBundle(bundleToQuiesce, callback); + } + } + + /** + * A Threadpool for running quiesce operations + */ + private final ExecutorService executor = new ThreadPoolExecutor(0, 10, 10, TimeUnit.SECONDS, new LinkedBlockingQueue(), new ThreadFactory() { + + public Thread newThread(Runnable r) { + Thread t = new Thread(r, "JPA-Context-ThreadPool"); + t.setDaemon(true); + return t; + } + }); + + /** The Global manager for persistence contexts */ + private final GlobalPersistenceManager mgr; + + + public QuiesceParticipantImpl(GlobalPersistenceManager mgr) { + this.mgr = mgr; + } + + + public void quiesce(QuiesceCallback qc, List arg1) { + //Run a quiesce operation for each bundle being quiesced + for(Bundle b : arg1) { + try { + executor.execute(new QuiesceBundle(qc, b, mgr)); + } catch (RejectedExecutionException re) { + + } + //If we are quiescing, then we need to quiesce this threadpool! + if(b.equals(mgr.getBundle())) + executor.shutdown(); + } + } + + /** + * Close down this object + */ + public void callback() { + executor.shutdown(); + try { + executor.awaitTermination(10, TimeUnit.SECONDS); + } catch (InterruptedException e) { + //We don't care + } + executor.shutdownNow(); + } +} Added: incubator/aries/trunk/jpa/jpa-container-context/src/main/java/org/apache/aries/jpa/container/context/transaction/impl/DestroyCallback.java URL: http://svn.apache.org/viewvc/incubator/aries/trunk/jpa/jpa-container-context/src/main/java/org/apache/aries/jpa/container/context/transaction/impl/DestroyCallback.java?rev=982543&view=auto ============================================================================== --- incubator/aries/trunk/jpa/jpa-container-context/src/main/java/org/apache/aries/jpa/container/context/transaction/impl/DestroyCallback.java (added) +++ incubator/aries/trunk/jpa/jpa-container-context/src/main/java/org/apache/aries/jpa/container/context/transaction/impl/DestroyCallback.java Thu Aug 5 11:13:55 2010 @@ -0,0 +1,26 @@ +/** + * 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.aries.jpa.container.context.transaction.impl; + +/** + * A callback to indicate that a destroy operation has completed + */ +public interface DestroyCallback { + public void callback(); +} Modified: incubator/aries/trunk/jpa/jpa-container-context/src/main/java/org/apache/aries/jpa/container/context/transaction/impl/JTAEntityManager.java URL: http://svn.apache.org/viewvc/incubator/aries/trunk/jpa/jpa-container-context/src/main/java/org/apache/aries/jpa/container/context/transaction/impl/JTAEntityManager.java?rev=982543&r1=982542&r2=982543&view=diff ============================================================================== --- incubator/aries/trunk/jpa/jpa-container-context/src/main/java/org/apache/aries/jpa/container/context/transaction/impl/JTAEntityManager.java (original) +++ incubator/aries/trunk/jpa/jpa-container-context/src/main/java/org/apache/aries/jpa/container/context/transaction/impl/JTAEntityManager.java Thu Aug 5 11:13:55 2010 @@ -19,6 +19,7 @@ package org.apache.aries.jpa.container.context.transaction.impl; import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; @@ -48,6 +49,12 @@ public class JTAEntityManager implements private final Map props; /** A registry for creating new persistence contexts */ private final JTAPersistenceContextRegistry reg; + /** The number of EntityManager instances that are open */ + private final AtomicLong instanceCount; + /** A callback for when we're quiescing */ + private final DestroyCallback callback; + + /** * The entity manager to use when there is no transaction. Note that there is one of these * per injection site. @@ -55,10 +62,13 @@ public class JTAEntityManager implements private EntityManager detachedManager = null; public JTAEntityManager(EntityManagerFactory factory, - Map properties, JTAPersistenceContextRegistry registry) { + Map properties, JTAPersistenceContextRegistry registry, AtomicLong activeCount, + DestroyCallback onDestroy) { emf = factory; props = properties; reg = registry; + instanceCount = activeCount; + callback = onDestroy; } /** @@ -70,10 +80,10 @@ public class JTAEntityManager implements private EntityManager getPersistenceContext(boolean forceTransaction) { if (forceTransaction) { - return reg.getCurrentPersistenceContext(emf, props); + return reg.getCurrentPersistenceContext(emf, props, instanceCount, callback); } else { if (reg.isTransactionActive()) { - return reg.getCurrentPersistenceContext(emf, props); + return reg.getCurrentPersistenceContext(emf, props, instanceCount, callback); } else { if(!!!reg.jtaIntegrationAvailable() && _logger.isDebugEnabled()) _logger.debug("No integration with JTA transactions is available. No transaction context is active."); Modified: incubator/aries/trunk/jpa/jpa-container-context/src/main/java/org/apache/aries/jpa/container/context/transaction/impl/JTAPersistenceContextRegistry.java URL: http://svn.apache.org/viewvc/incubator/aries/trunk/jpa/jpa-container-context/src/main/java/org/apache/aries/jpa/container/context/transaction/impl/JTAPersistenceContextRegistry.java?rev=982543&r1=982542&r2=982543&view=diff ============================================================================== --- incubator/aries/trunk/jpa/jpa-container-context/src/main/java/org/apache/aries/jpa/container/context/transaction/impl/JTAPersistenceContextRegistry.java (original) +++ incubator/aries/trunk/jpa/jpa-container-context/src/main/java/org/apache/aries/jpa/container/context/transaction/impl/JTAPersistenceContextRegistry.java Thu Aug 5 11:13:55 2010 @@ -20,7 +20,8 @@ package org.apache.aries.jpa.container.c import java.util.IdentityHashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; @@ -28,14 +29,16 @@ import javax.persistence.TransactionRequ import javax.transaction.Synchronization; import javax.transaction.TransactionSynchronizationRegistry; +import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; +import org.osgi.util.tracker.ServiceTracker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class is used to manage the lifecycle of JTA peristence contexts */ -public final class JTAPersistenceContextRegistry { +public final class JTAPersistenceContextRegistry extends ServiceTracker { /** Logger */ private static final Logger _logger = LoggerFactory.getLogger("org.apache.aries.jpa.container.context"); /** The unique key we use to find our Map */ @@ -62,13 +65,15 @@ public final class JTAPersistenceContext * The transaction synchronization registry, used to determine the currently * active transaction, and to register for post-commit cleanup. */ - private TransactionSynchronizationRegistry tranRegistry; + private final AtomicReference tranRegistry = new AtomicReference(); - /** - * A flag to indicate whether the {@link TransactionSynchronizationRegistry} is available. - * The initial value is false, as defined by {@link AtomicBoolean#AtomicBoolean()}. - */ - private final AtomicBoolean registryAvailable = new AtomicBoolean(); + /** The reference for our TSR service */ + private AtomicReference tranRegistryRef = new AtomicReference(); + + public JTAPersistenceContextRegistry(BundleContext context) { + super(context, TransactionSynchronizationRegistry.class.getName(), null); + open(); + } /** * Get a PersistenceContext for the current transaction. The persistence context will @@ -77,13 +82,17 @@ public final class JTAPersistenceContext * @param persistenceUnit The peristence unit to create the persitence context from * @param properties Any properties that should be passed on the call to {@code createEntityManager()}. * The properties are NOT used for retrieving an already created persistence context. + * @param activeCount The AtomicLong for counting instances + * @param cbk A callback called when the instance is destroyed * * @return A persistence context associated with the current transaction. Note that this will * need to be wrappered to obey the JPA spec by throwing the correct exceptions * @throws {@link TransactionRequiredException} if there is no active transaction. */ @SuppressWarnings("unchecked") - public final EntityManager getCurrentPersistenceContext(EntityManagerFactory persistenceUnit, Map properties) throws TransactionRequiredException + public final EntityManager getCurrentPersistenceContext( + EntityManagerFactory persistenceUnit, Map properties, AtomicLong activeCount, + DestroyCallback cbk) throws TransactionRequiredException { //There will only ever be one thread associated with a transaction at a given time //As a result, it is only the outer map that needs to be thread safe. @@ -98,11 +107,11 @@ public final class JTAPersistenceContext } } EntityManager toReturn = null; - + TransactionSynchronizationRegistry tsr = tranRegistry.get(); //Get hold of the Map. If there is no Map already registered then add one. //We don't need to worry about a race condition, as no other thread will //share our transaction and be able to access our Map - Map contextsForTransaction = (Map) tranRegistry.getResource(EMF_MAP_KEY); + Map contextsForTransaction = (Map) tsr.getResource(EMF_MAP_KEY); //If we have a map then find an EntityManager, else create a new Map add it to the registry, and register for cleanup if(contextsForTransaction != null) { @@ -110,10 +119,10 @@ public final class JTAPersistenceContext } else { contextsForTransaction = new IdentityHashMap(); try { - tranRegistry.putResource(EMF_MAP_KEY, contextsForTransaction); + tsr.putResource(EMF_MAP_KEY, contextsForTransaction); } catch (IllegalStateException e) { - _logger.warn("Unable to create a persistence context for the transaction {} because the is not active", new Object[] {tranRegistry.getTransactionKey()}); - throw new TransactionRequiredException("Unable to assiociate resources with transaction " + tranRegistry.getTransactionKey()); + _logger.warn("Unable to create a persistence context for the transaction {} because the is not active", new Object[] {tsr.getTransactionKey()}); + throw new TransactionRequiredException("Unable to assiociate resources with transaction " + tsr.getTransactionKey()); } } @@ -121,18 +130,19 @@ public final class JTAPersistenceContext if(toReturn == null) { toReturn = (properties == null) ? persistenceUnit.createEntityManager() : persistenceUnit.createEntityManager(properties); if(_logger.isDebugEnabled()) - _logger.debug("Created a new persistence context {} for transaction {}.", new Object[] {toReturn, tranRegistry.getTransactionKey()}); + _logger.debug("Created a new persistence context {} for transaction {}.", new Object[] {toReturn, tsr.getTransactionKey()}); try { - tranRegistry.registerInterposedSynchronization(new EntityManagerClearUp(toReturn)); + tsr.registerInterposedSynchronization(new EntityManagerClearUp(toReturn, activeCount, cbk)); } catch (IllegalStateException e) { - _logger.warn("No persistence context could be created as the JPA container could not register a synchronization with the transaction {}.", new Object[] {tranRegistry.getTransactionKey()}); + _logger.warn("No persistence context could be created as the JPA container could not register a synchronization with the transaction {}.", new Object[] {tsr.getTransactionKey()}); toReturn.close(); - throw new TransactionRequiredException("Unable to synchronize with transaction " + tranRegistry.getTransactionKey()); + throw new TransactionRequiredException("Unable to synchronize with transaction " + tsr.getTransactionKey()); } contextsForTransaction.put(persistenceUnit, toReturn); + activeCount.incrementAndGet(); } else { if(_logger.isDebugEnabled()) - _logger.debug("Re-using a persistence context for transaction " + tranRegistry.getTransactionKey()); + _logger.debug("Re-using a persistence context for transaction " + tsr.getTransactionKey()); } return toReturn; } @@ -143,15 +153,8 @@ public final class JTAPersistenceContext */ public final boolean isTransactionActive() { - return registryAvailable.get() && tranRegistry.getTransactionKey() != null; - } - - /** - * Provide a {@link TransactionSynchronizationRegistry} to use - * @param tranRegistry - */ - public final void setTranRegistry(TransactionSynchronizationRegistry tranRegistry) { - this.tranRegistry = tranRegistry; + TransactionSynchronizationRegistry tsr = tranRegistry.get(); + return tsr != null && tsr.getTransactionKey() != null; } /** @@ -161,34 +164,93 @@ public final class JTAPersistenceContext */ public final boolean jtaIntegrationAvailable() { - return registryAvailable.get(); + return tranRegistry.get() != null; } /** - * Called by the blueprint container to indicate that a new {@link TransactionSynchronizationRegistry} - * will be used by the runtime + * Called by service tracker to indicate that a new {@link TransactionSynchronizationRegistry} + * is available in the runtime * @param ref */ - public final void addRegistry(ServiceReference ref) { - boolean oldValue = registryAvailable.getAndSet(true); - if(oldValue) { - _logger.warn("The TransactionSynchronizationRegistry used to manage persistence contexts has been replaced." + - " The new TransactionSynchronizationRegistry, {}, will now be used to manage persistence contexts." + - " Managed persistence contexts may not work correctly unless the runtime uses the new JTA Transaction services implementation" + - " to manage transactions.", new Object[] {ref}); - } else { - _logger.info("A TransactionSynchronizationRegistry service is now available in the runtime. Managed persistence contexts will now" + - "integrate with JTA transactions using {}.", new Object[] {ref}); + @Override + public final Object addingService(ServiceReference ref) { + + if(tranRegistryRef.compareAndSet(null, ref)) + { + TransactionSynchronizationRegistry tsr = (TransactionSynchronizationRegistry) context.getService(ref); + if(tsr != null) { + if(tranRegistry.compareAndSet(null, tsr)) { + _logger.info("A TransactionSynchronizationRegistry service is now available in the runtime. Managed persistence contexts will now" + + "integrate with JTA transactions using {}.", new Object[] {ref}); + } + else + { + tranRegistry.set(tsr); + _logger.warn("The TransactionSynchronizationRegistry used to manage persistence contexts has been replaced." + + " The new TransactionSynchronizationRegistry, {}, will now be used to manage persistence contexts." + + " Managed persistence contexts may not work correctly unless the runtime uses the new JTA Transaction services implementation" + + " to manage transactions.", new Object[] {ref}); + } + } else { + tranRegistryRef.compareAndSet(ref, null); + } } + return ref; } - public final void removeRegistry(ServiceReference ref) { - registryAvailable.set(false); - _logger.warn("The TransactionSynchronizationRegistry used to manage persistence contexts is no longer available." + - " Managed persistence contexts will no longer be able to integrate with JTA transactions, and will behave as if" + - " no there is no transaction context at all times until a new TransactionSynchronizationRegistry is available." + - " Applications using managed persistence contexts may not work correctly until a new JTA Transaction services" + - " implementation is available."); + @Override + public final void removedService(ServiceReference reference, Object o) { + if(tranRegistryRef.get() == reference) { + //Our reference is going away, find a new one + ServiceReference[] refs = getServiceReferences(); + ServiceReference chosenRef = null; + TransactionSynchronizationRegistry replacement = null; + if(refs != null) { + for(ServiceReference ref : refs) { + if(ref != reference) { + replacement = (TransactionSynchronizationRegistry) context.getService(ref); + if(replacement != null) { + chosenRef = ref; + break; + } + } + } + } + + if(replacement == null) { + TransactionSynchronizationRegistry old = tranRegistry.get(); + tranRegistryRef.set(null); + tranRegistry.compareAndSet(old, null); + + _logger.warn("The TransactionSynchronizationRegistry used to manage persistence contexts is no longer available." + + " Managed persistence contexts will no longer be able to integrate with JTA transactions, and will behave as if" + + " no there is no transaction context at all times until a new TransactionSynchronizationRegistry is available." + + " Applications using managed persistence contexts may not work correctly until a new JTA Transaction services" + + " implementation is available."); + } else { + tranRegistryRef.set(chosenRef); + tranRegistry.set(replacement); + _logger.warn("The TransactionSynchronizationRegistry used to manage persistence contexts has been replaced." + + " The new TransactionSynchronizationRegistry, {}, will now be used to manage persistence contexts." + + " Managed persistence contexts may not work correctly unless the runtime uses the new JTA Transaction services implementation" + + " to manage transactions.", new Object[] {chosenRef}); + } + context.ungetService(reference); + //If there was no replacement before, check again. This closes the short window if + //add and remove happen simultaneously + if(replacement == null) { + ServiceReference[] retryRefs = getServiceReferences(); + if(refs != null) { + for(ServiceReference r : retryRefs) { + if(r != reference) { + addingService(r); + if(tranRegistryRef.get() != null) + break; + } + } + } + } + } } /** @@ -199,13 +261,19 @@ public final class JTAPersistenceContext private final EntityManager context; + private final AtomicLong activeCount; + + private final DestroyCallback callback; + /** * Create a Synchronization to clear up our EntityManagers * @param em */ - public EntityManagerClearUp(EntityManager em) + public EntityManagerClearUp(EntityManager em, AtomicLong instanceCount, DestroyCallback cbk) { context = em; + activeCount = instanceCount; + callback = cbk; } public final void beforeCompletion() { @@ -217,10 +285,16 @@ public final class JTAPersistenceContext if(_logger.isDebugEnabled()) _logger.debug("Clearing up EntityManager {} as the transaction has completed.", new Object[] {context}); try { - context.close(); - } catch (Exception e) { - _logger.warn("There was an error when the container closed an EntityManager", context); + activeCount.decrementAndGet(); + callback.callback(); + } finally { + try{ + context.close(); + } catch (Exception e) { + _logger.warn("There was an error when the container closed an EntityManager", context); + } } } } + } Modified: incubator/aries/trunk/jpa/jpa-container-context/src/test/java/org/apache/aries/jpa/container/context/impl/GlobalPersistenceManagerTest.java URL: http://svn.apache.org/viewvc/incubator/aries/trunk/jpa/jpa-container-context/src/test/java/org/apache/aries/jpa/container/context/impl/GlobalPersistenceManagerTest.java?rev=982543&r1=982542&r2=982543&view=diff ============================================================================== --- incubator/aries/trunk/jpa/jpa-container-context/src/test/java/org/apache/aries/jpa/container/context/impl/GlobalPersistenceManagerTest.java (original) +++ incubator/aries/trunk/jpa/jpa-container-context/src/test/java/org/apache/aries/jpa/container/context/impl/GlobalPersistenceManagerTest.java Thu Aug 5 11:13:55 2010 @@ -45,7 +45,7 @@ public class GlobalPersistenceManagerTes private Bundle framework; @Before - public void setup() + public void setup() throws Exception { framework = Skeleton.newMock(new BundleMock("framework", new Hashtable()), Bundle.class); @@ -59,8 +59,7 @@ public class GlobalPersistenceManagerTes Skeleton.getSkeleton(otherClient).setReturnValue(new MethodCall(Bundle.class, "getBundleContext"), ctx); sut = new GlobalPersistenceManager(); - sut.setRegistry(new JTAPersistenceContextRegistry()); - + sut.start(ctx); Skeleton.getSkeleton(framework.getBundleContext()).setReturnValue( new MethodCall(BundleContext.class, "getBundles"), new Bundle[] {framework, client, otherClient}); } Modified: incubator/aries/trunk/jpa/jpa-container-context/src/test/java/org/apache/aries/jpa/container/context/impl/PersistenceContextManagerTest.java URL: http://svn.apache.org/viewvc/incubator/aries/trunk/jpa/jpa-container-context/src/test/java/org/apache/aries/jpa/container/context/impl/PersistenceContextManagerTest.java?rev=982543&r1=982542&r2=982543&view=diff ============================================================================== --- incubator/aries/trunk/jpa/jpa-container-context/src/test/java/org/apache/aries/jpa/container/context/impl/PersistenceContextManagerTest.java (original) +++ incubator/aries/trunk/jpa/jpa-container-context/src/test/java/org/apache/aries/jpa/container/context/impl/PersistenceContextManagerTest.java Thu Aug 5 11:13:55 2010 @@ -26,11 +26,16 @@ import java.util.HashMap; import java.util.Hashtable; import javax.persistence.EntityManagerFactory; +import javax.transaction.TransactionSynchronizationRegistry; import org.apache.aries.jpa.container.PersistenceUnitConstants; import org.apache.aries.jpa.container.context.PersistenceContextProvider; +import org.apache.aries.jpa.container.context.transaction.impl.DestroyCallback; +import org.apache.aries.jpa.container.context.transaction.impl.JTAPersistenceContextRegistry; +import org.apache.aries.jpa.container.context.transaction.impl.TranSyncRegistryMock; import org.apache.aries.mocks.BundleContextMock; import org.apache.aries.mocks.BundleMock; +import org.apache.aries.unittest.mocks.MethodCall; import org.apache.aries.unittest.mocks.Skeleton; import org.junit.After; import org.junit.Before; @@ -65,7 +70,7 @@ public class PersistenceContextManagerTe emf1 = Skeleton.newMock(EntityManagerFactory.class); emf2 = Skeleton.newMock(EntityManagerFactory.class); context = Skeleton.newMock(new BundleMock("system.bundle", new Hashtable()), Bundle.class).getBundleContext(); - mgr = new PersistenceContextManager(context, null); + mgr = new PersistenceContextManager(context, new JTAPersistenceContextRegistry(context)); mgr.open(); } @@ -92,7 +97,7 @@ public class PersistenceContextManagerTe mgr.registerContext(unitName, client1, new HashMap()); - assertContextRegistered(unitName); + assertUniqueContextRegistered(unitName); } /** @@ -142,7 +147,7 @@ public class PersistenceContextManagerTe reg1 = registerUnit(emf1, unitName, TRUE); - assertContextRegistered(unitName); + assertUniqueContextRegistered(unitName); } /** @@ -203,11 +208,11 @@ public class PersistenceContextManagerTe mgr.registerContext("unit", client1, new HashMap()); - assertContextRegistered("unit"); + assertUniqueContextRegistered("unit"); mgr.unregisterContext("context", client1); - assertContextRegistered("unit"); + assertUniqueContextRegistered("unit"); } /** @@ -220,9 +225,9 @@ public class PersistenceContextManagerTe { testAddDifferentContext(); reg2 = registerUnit(emf2, "context", TRUE); - assertContextRegistered("context"); + assertUniqueContextRegistered("context"); reg1.unregister(); - assertContextRegistered("context"); + assertUniqueContextRegistered("context"); reg2.unregister(); assertNoContextRegistered(); } @@ -237,15 +242,272 @@ public class PersistenceContextManagerTe testContextThenUnit(); mgr.registerContext("unit", client2, new HashMap()); - assertContextRegistered("unit"); + assertUniqueContextRegistered("unit"); mgr.unregisterContext("unit", client1); - assertContextRegistered("unit"); + assertUniqueContextRegistered("unit"); mgr.unregisterContext("unit", client2); assertNoContextRegistered(); } + /** + * Test that we can quiesce an unused context + * @throws InvalidSyntaxException + */ + @Test + public void testNoOpQuiesce() throws InvalidSyntaxException + { + DestroyCallback cbk = Skeleton.newMock(DestroyCallback.class); + Bundle b = context.getBundle(); + mgr.quiesceUnits(b, cbk); + + Skeleton.getSkeleton(cbk).assertCalledExactNumberOfTimes(new MethodCall(DestroyCallback.class, + "callback"), 1); + + assertNoContextRegistered(); + } + + /** + * Test that we can quiesce an unused context + * @throws InvalidSyntaxException + */ + @Test + public void testBasicQuiesce() throws InvalidSyntaxException + { + testContextThenUnit(); + DestroyCallback cbk = Skeleton.newMock(DestroyCallback.class); + Bundle b = context.getBundle(); + mgr.quiesceUnits(b, cbk); + + Skeleton.getSkeleton(cbk).assertCalledExactNumberOfTimes(new MethodCall(DestroyCallback.class, + "callback"), 1); + + assertNoContextRegistered(); + } + + /** + * Test that we can quiesce multiple contexts + * @throws InvalidSyntaxException + */ + @Test + public void testMultipleContextQuiesce() throws InvalidSyntaxException + { + assertNoContextRegistered(); + + reg1 = registerUnit(emf1, "unit", TRUE); + mgr.registerContext("unit", client1, new HashMap()); + + reg2 = registerUnit(emf1, "unit2", TRUE); + mgr.registerContext("unit2", client2, new HashMap()); + + DestroyCallback cbk = Skeleton.newMock(DestroyCallback.class); + Bundle b = context.getBundle(); + mgr.quiesceUnits(b, cbk); + + Skeleton.getSkeleton(cbk).assertCalledExactNumberOfTimes(new MethodCall(DestroyCallback.class, + "callback"), 1); + + assertNoContextRegistered(); + } + + /** + * Test that we can quiesce multiple contexts + * @throws InvalidSyntaxException + */ + @Test + public void testActiveContextsQuiesce() throws InvalidSyntaxException + { + TranSyncRegistryMock backingTSR = new TranSyncRegistryMock(); + TransactionSynchronizationRegistry tsr = Skeleton.newMock(backingTSR, TransactionSynchronizationRegistry.class); + + context.registerService(TransactionSynchronizationRegistry.class.getName(), tsr, new Hashtable()); + + assertNoContextRegistered(); + + reg1 = registerUnit(emf1, "unit", TRUE); + mgr.registerContext("unit", client1, new HashMap()); + + reg2 = registerUnit(emf2, "unit2", TRUE); + mgr.registerContext("unit2", client2, new HashMap()); + + ServiceReference[] refs = context.getServiceReferences(EntityManagerFactory.class.getName(),"(&(" + + PersistenceContextProvider.PROXY_FACTORY_EMF_ATTRIBUTE + "=true)(osgi.unit.name=unit))"); + + assertEquals("Wrong number of services found", 1, refs.length); + + ServiceReference[] refs2 = context.getServiceReferences(EntityManagerFactory.class.getName(),"(&(" + + PersistenceContextProvider.PROXY_FACTORY_EMF_ATTRIBUTE + "=true)(osgi.unit.name=unit2))"); + + assertEquals("Wrong number of services found", 1, refs2.length); + + EntityManagerFactory emf = (EntityManagerFactory) context.getService(refs[0]); + + EntityManagerFactory emf2 = (EntityManagerFactory) context.getService(refs2[0]); + + backingTSR.setTransactionKey("new"); + + emf.createEntityManager().persist(new Object()); + emf2.createEntityManager().persist(new Object()); + + + DestroyCallback cbk = Skeleton.newMock(DestroyCallback.class); + Bundle b = context.getBundle(); + mgr.quiesceUnits(b, cbk); + + Skeleton.getSkeleton(cbk).assertNotCalled(new MethodCall(DestroyCallback.class, + "callback")); + + backingTSR.setTransactionKey("new2"); + + emf2.createEntityManager().persist(new Object()); + + Skeleton.getSkeleton(cbk).assertNotCalled(new MethodCall(DestroyCallback.class, + "callback")); + + backingTSR.afterCompletion("new"); + + Skeleton.getSkeleton(cbk).assertNotCalled(new MethodCall(DestroyCallback.class, + "callback")); + + assertUniqueContextRegistered("unit2"); + + backingTSR.afterCompletion("new2"); + + Skeleton.getSkeleton(cbk).assertCalledExactNumberOfTimes(new MethodCall(DestroyCallback.class, + "callback"), 1); + + assertNoContextRegistered(); + } + + /** + * Test that we can quiesce an unused context + * @throws InvalidSyntaxException + */ + @Test + public void testNoOpQuiesceAll() throws InvalidSyntaxException + { + DestroyCallback cbk = Skeleton.newMock(DestroyCallback.class); + Bundle b = context.getBundle(); + mgr.quiesceAllUnits(cbk); + + Skeleton.getSkeleton(cbk).assertCalledExactNumberOfTimes(new MethodCall(DestroyCallback.class, + "callback"), 1); + + assertNoContextRegistered(); + } + + /** + * Test that we can quiesce an unused context + * @throws InvalidSyntaxException + */ + @Test + public void testBasicQuiesceAll() throws InvalidSyntaxException + { + testContextThenUnit(); + DestroyCallback cbk = Skeleton.newMock(DestroyCallback.class); + Bundle b = context.getBundle(); + mgr.quiesceAllUnits(cbk); + + Skeleton.getSkeleton(cbk).assertCalledExactNumberOfTimes(new MethodCall(DestroyCallback.class, + "callback"), 1); + + assertNoContextRegistered(); + } + + /** + * Test that we can quiesce multiple contexts + * @throws InvalidSyntaxException + */ + @Test + public void testMultipleContextQuiesceAll() throws InvalidSyntaxException + { + assertNoContextRegistered(); + + reg1 = registerUnit(emf1, "unit", TRUE); + mgr.registerContext("unit", client1, new HashMap()); + + reg2 = registerUnit(emf1, "unit2", TRUE); + mgr.registerContext("unit2", client2, new HashMap()); + + DestroyCallback cbk = Skeleton.newMock(DestroyCallback.class); + Bundle b = context.getBundle(); + mgr.quiesceAllUnits(cbk); + + Skeleton.getSkeleton(cbk).assertCalledExactNumberOfTimes(new MethodCall(DestroyCallback.class, + "callback"), 1); + + assertNoContextRegistered(); + } + + /** + * Test that we can quiesce multiple contexts + * @throws InvalidSyntaxException + */ + @Test + public void testActiveContextsQuiesceAll() throws InvalidSyntaxException + { + TranSyncRegistryMock backingTSR = new TranSyncRegistryMock(); + TransactionSynchronizationRegistry tsr = Skeleton.newMock(backingTSR, TransactionSynchronizationRegistry.class); + + context.registerService(TransactionSynchronizationRegistry.class.getName(), tsr, new Hashtable()); + + assertNoContextRegistered(); + + reg1 = registerUnit(emf1, "unit", TRUE); + mgr.registerContext("unit", client1, new HashMap()); + + reg2 = registerUnit(emf2, "unit2", TRUE); + mgr.registerContext("unit2", client2, new HashMap()); + + ServiceReference[] refs = context.getServiceReferences(EntityManagerFactory.class.getName(),"(&(" + + PersistenceContextProvider.PROXY_FACTORY_EMF_ATTRIBUTE + "=true)(osgi.unit.name=unit))"); + + assertEquals("Wrong number of services found", 1, refs.length); + + ServiceReference[] refs2 = context.getServiceReferences(EntityManagerFactory.class.getName(),"(&(" + + PersistenceContextProvider.PROXY_FACTORY_EMF_ATTRIBUTE + "=true)(osgi.unit.name=unit2))"); + + assertEquals("Wrong number of services found", 1, refs2.length); + + EntityManagerFactory emf = (EntityManagerFactory) context.getService(refs[0]); + + EntityManagerFactory emf2 = (EntityManagerFactory) context.getService(refs2[0]); + + backingTSR.setTransactionKey("new"); + + emf.createEntityManager().persist(new Object()); + emf2.createEntityManager().persist(new Object()); + + + DestroyCallback cbk = Skeleton.newMock(DestroyCallback.class); + Bundle b = context.getBundle(); + mgr.quiesceAllUnits(cbk); + + Skeleton.getSkeleton(cbk).assertNotCalled(new MethodCall(DestroyCallback.class, + "callback")); + + backingTSR.setTransactionKey("new2"); + + emf2.createEntityManager().persist(new Object()); + + Skeleton.getSkeleton(cbk).assertNotCalled(new MethodCall(DestroyCallback.class, + "callback")); + + backingTSR.afterCompletion("new"); + + Skeleton.getSkeleton(cbk).assertNotCalled(new MethodCall(DestroyCallback.class, + "callback")); + + assertUniqueContextRegistered("unit2"); + + backingTSR.afterCompletion("new2"); + + Skeleton.getSkeleton(cbk).assertCalledExactNumberOfTimes(new MethodCall(DestroyCallback.class, + "callback"), 1); + + assertNoContextRegistered(); + } private ServiceRegistration registerUnit(EntityManagerFactory emf, String name, Boolean managed) { @@ -270,7 +532,7 @@ public class PersistenceContextManagerTe assertNull(refs); } - private void assertContextRegistered(String name) throws InvalidSyntaxException { + private void assertUniqueContextRegistered(String name) throws InvalidSyntaxException { BundleContextMock.assertServiceExists(EntityManagerFactory.class.getName()); ServiceReference[] refs = context.getServiceReferences(EntityManagerFactory.class.getName(), "("+PersistenceContextProvider.PROXY_FACTORY_EMF_ATTRIBUTE+"=*)"); Modified: incubator/aries/trunk/jpa/jpa-container-context/src/test/java/org/apache/aries/jpa/container/context/transaction/impl/JTAPersistenceContextRegistryTest.java URL: http://svn.apache.org/viewvc/incubator/aries/trunk/jpa/jpa-container-context/src/test/java/org/apache/aries/jpa/container/context/transaction/impl/JTAPersistenceContextRegistryTest.java?rev=982543&r1=982542&r2=982543&view=diff ============================================================================== --- incubator/aries/trunk/jpa/jpa-container-context/src/test/java/org/apache/aries/jpa/container/context/transaction/impl/JTAPersistenceContextRegistryTest.java (original) +++ incubator/aries/trunk/jpa/jpa-container-context/src/test/java/org/apache/aries/jpa/container/context/transaction/impl/JTAPersistenceContextRegistryTest.java Thu Aug 5 11:13:55 2010 @@ -18,83 +18,30 @@ */ package org.apache.aries.jpa.container.context.transaction.impl; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.TransactionRequiredException; -import javax.transaction.Status; -import javax.transaction.Synchronization; import javax.transaction.TransactionSynchronizationRegistry; import org.apache.aries.unittest.mocks.MethodCall; import org.apache.aries.unittest.mocks.Skeleton; import org.junit.Before; import org.junit.Test; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; public class JTAPersistenceContextRegistryTest { - private static class TranSyncRegistryMock - { - private String key; - - private Map> syncs = new HashMap>(); - - private Map> resources = new HashMap>(); - - public void setTransactionKey(String s) - { - key = s; - } - - public Object getTransactionKey() { - return key; - } - - public void registerInterposedSynchronization(Synchronization arg0) { - List list = syncs.get(key); - if(list == null) { - list = new ArrayList(); - syncs.put(key, list); - } - list.add(arg0); - } - - public Object getResource(Object o) { - Object toReturn = null; - Map map = resources.get(key); - if(map != null) - toReturn = map.get(o); - return toReturn; - } - - public void putResource(Object resourceKey, Object value) { - Map map = resources.get(key); - if(map == null) { - map = new HashMap(); - resources.put(key, map); - } - map.put(resourceKey, value); - } - - - public void afterCompletion(String s) - { - for(Synchronization sync : syncs.get(s)) - sync.afterCompletion(Status.STATUS_COMMITTED); - - resources.remove(s); - } - } - private TranSyncRegistryMock reg; private EntityManagerFactory emf1; @@ -103,11 +50,17 @@ public class JTAPersistenceContextRegist private Map props2; private JTAPersistenceContextRegistry contexts; + private BundleContext ctx; + private TransactionSynchronizationRegistry tsr; + + private ServiceReference ref; @Before public void setup() { reg = new TranSyncRegistryMock(); + tsr = Skeleton.newMock(reg, TransactionSynchronizationRegistry.class); + ctx = Skeleton.newMock(BundleContext.class); props1 = new HashMap(); props1.put("prop1", "value1"); @@ -130,9 +83,11 @@ public class JTAPersistenceContextRegist "createEntityManager", props2), Skeleton.newMock(EntityManager.class)); - contexts = new JTAPersistenceContextRegistry(); - contexts.setTranRegistry(Skeleton.newMock(reg, TransactionSynchronizationRegistry.class)); - contexts.addRegistry(null); + contexts = new JTAPersistenceContextRegistry(ctx); + ref = Skeleton.newMock(ServiceReference.class); + Skeleton.getSkeleton(ctx).setReturnValue(new MethodCall(BundleContext.class, + "getService", ref), tsr); + contexts.addingService(ref); } @Test @@ -150,50 +105,65 @@ public class JTAPersistenceContextRegist @Test public void testMultiGetsOneTran() { + AtomicLong useCount = new AtomicLong(0); + DestroyCallback cbk = Skeleton.newMock(DestroyCallback.class); reg.setTransactionKey(""); - EntityManager em1a = contexts.getCurrentPersistenceContext(emf1, props1); - EntityManager em1b = contexts.getCurrentPersistenceContext(emf1, props1); + EntityManager em1a = contexts.getCurrentPersistenceContext(emf1, props1, useCount, cbk); + EntityManager em1b = contexts.getCurrentPersistenceContext(emf1, props1, useCount, cbk); Skeleton.getSkeleton(emf1).assertCalledExactNumberOfTimes(new MethodCall(EntityManagerFactory.class, "createEntityManager", props1), 1); Skeleton.getSkeleton(emf1).assertNotCalled(new MethodCall(EntityManagerFactory.class, "createEntityManager")); Skeleton.getSkeleton(em1a).assertNotCalled(new MethodCall(EntityManager.class, "close")); assertSame("We should get the same delegate!", em1a, em1b); + assertEquals("Expected only one creation", 1, useCount.get()); + Skeleton.getSkeleton(cbk).assertSkeletonNotCalled(); - EntityManager em2a = contexts.getCurrentPersistenceContext(emf2, props1); - EntityManager em2b = contexts.getCurrentPersistenceContext(emf2, props1); + EntityManager em2a = contexts.getCurrentPersistenceContext(emf2, props1, useCount, cbk); + EntityManager em2b = contexts.getCurrentPersistenceContext(emf2, props1, useCount, cbk); Skeleton.getSkeleton(emf2).assertCalledExactNumberOfTimes(new MethodCall(EntityManagerFactory.class, "createEntityManager", props1), 1); Skeleton.getSkeleton(emf2).assertNotCalled(new MethodCall(EntityManagerFactory.class, "createEntityManager")); Skeleton.getSkeleton(em2a).assertNotCalled(new MethodCall(EntityManager.class, "close")); assertSame("We should get the same delegate!", em2a, em2b); + assertEquals("Expected a second creation", 2, useCount.get()); + Skeleton.getSkeleton(cbk).assertSkeletonNotCalled(); reg.afterCompletion(""); Skeleton.getSkeleton(em1a).assertCalledExactNumberOfTimes(new MethodCall(EntityManager.class, "close"),1); Skeleton.getSkeleton(em2a).assertCalledExactNumberOfTimes(new MethodCall(EntityManager.class, "close"),1); + assertEquals("Expected creations to be uncounted", 0, useCount.get()); + Skeleton.getSkeleton(cbk).assertCalledExactNumberOfTimes(new MethodCall(DestroyCallback.class, + "callback"), 2); } @Test public void testMultiGetsMultiTrans() { + AtomicLong useCount = new AtomicLong(0); + DestroyCallback cbk = Skeleton.newMock(DestroyCallback.class); + reg.setTransactionKey("a"); - EntityManager em1a = contexts.getCurrentPersistenceContext(emf1, props1); + EntityManager em1a = contexts.getCurrentPersistenceContext(emf1, props1, useCount, cbk); reg.setTransactionKey("b"); - EntityManager em1b = contexts.getCurrentPersistenceContext(emf1, props2); + EntityManager em1b = contexts.getCurrentPersistenceContext(emf1, props2, useCount, cbk); Skeleton.getSkeleton(emf1).assertCalledExactNumberOfTimes(new MethodCall(EntityManagerFactory.class, "createEntityManager", props1), 1); Skeleton.getSkeleton(emf1).assertCalledExactNumberOfTimes(new MethodCall(EntityManagerFactory.class, "createEntityManager", props2), 1); - + assertNotSame("We should not get the same delegate!", em1a, em1b); Skeleton.getSkeleton(em1a).assertNotCalled(new MethodCall(EntityManager.class, "close")); Skeleton.getSkeleton(em1b).assertNotCalled(new MethodCall(EntityManager.class, "close")); + assertEquals("Expected two creations", 2, useCount.get()); + Skeleton.getSkeleton(cbk).assertSkeletonNotCalled(); + reg.setTransactionKey("a"); - EntityManager em2a = contexts.getCurrentPersistenceContext(emf2, props1); + EntityManager em2a = contexts.getCurrentPersistenceContext(emf2, props1, useCount, cbk); reg.setTransactionKey("b"); - EntityManager em2b = contexts.getCurrentPersistenceContext(emf2, props2); + EntityManager em2b = contexts.getCurrentPersistenceContext(emf2, props2, useCount, cbk); Skeleton.getSkeleton(emf2).assertCalledExactNumberOfTimes(new MethodCall(EntityManagerFactory.class, "createEntityManager", props1), 1); Skeleton.getSkeleton(emf2).assertCalledExactNumberOfTimes(new MethodCall(EntityManagerFactory.class, "createEntityManager", props2), 1); @@ -203,6 +173,9 @@ public class JTAPersistenceContextRegist Skeleton.getSkeleton(em2a).assertNotCalled(new MethodCall(EntityManager.class, "close")); Skeleton.getSkeleton(em2b).assertNotCalled(new MethodCall(EntityManager.class, "close")); + assertEquals("Expected four creations", 4, useCount.get()); + Skeleton.getSkeleton(cbk).assertSkeletonNotCalled(); + reg.setTransactionKey("b"); reg.afterCompletion("b"); @@ -211,6 +184,10 @@ public class JTAPersistenceContextRegist Skeleton.getSkeleton(em2a).assertNotCalled(new MethodCall(EntityManager.class, "close")); Skeleton.getSkeleton(em2b).assertCalledExactNumberOfTimes(new MethodCall(EntityManager.class, "close"), 1); + assertEquals("Expected two uncreations", 2, useCount.get()); + Skeleton.getSkeleton(cbk).assertCalledExactNumberOfTimes(new MethodCall(DestroyCallback.class, + "callback"), 2); + reg.setTransactionKey("a"); reg.afterCompletion("a"); @@ -218,41 +195,55 @@ public class JTAPersistenceContextRegist Skeleton.getSkeleton(em1b).assertCalledExactNumberOfTimes(new MethodCall(EntityManager.class, "close"), 1); Skeleton.getSkeleton(em2a).assertCalledExactNumberOfTimes(new MethodCall(EntityManager.class, "close"), 1); Skeleton.getSkeleton(em2b).assertCalledExactNumberOfTimes(new MethodCall(EntityManager.class, "close"), 1); + assertEquals("Expected no remaining instances", 0, useCount.get()); + Skeleton.getSkeleton(cbk).assertCalledExactNumberOfTimes(new MethodCall(DestroyCallback.class, + "callback"), 4); } @Test public void testNoTranSyncRegistry() { - JTAPersistenceContextRegistry registry = new JTAPersistenceContextRegistry(); - //blueprint will still call our setter - TransactionSynchronizationRegistry tranSyncReg = Skeleton.newMock(reg, TransactionSynchronizationRegistry.class); - registry.setTranRegistry(tranSyncReg); + JTAPersistenceContextRegistry registry = new JTAPersistenceContextRegistry(ctx); reg.setTransactionKey(null); assertFalse(registry.jtaIntegrationAvailable()); assertFalse(registry.isTransactionActive()); - Skeleton.getSkeleton(tranSyncReg).assertSkeletonNotCalled(); + Skeleton.getSkeleton(tsr).assertSkeletonNotCalled(); reg.setTransactionKey(""); assertFalse(registry.jtaIntegrationAvailable()); assertFalse(registry.isTransactionActive()); - Skeleton.getSkeleton(tranSyncReg).assertSkeletonNotCalled(); + reg.setTransactionKey(null); + contexts.removedService(ref, ref); + + assertFalse(contexts.jtaIntegrationAvailable()); + assertFalse(contexts.isTransactionActive()); + + Skeleton.getSkeleton(tsr).assertSkeletonNotCalled(); + + reg.setTransactionKey(""); + + assertFalse(contexts.jtaIntegrationAvailable()); + assertFalse(contexts.isTransactionActive()); + + + Skeleton.getSkeleton(tsr).assertSkeletonNotCalled(); } @Test(expected=TransactionRequiredException.class) public void testGetNoTran() { reg.setTransactionKey(null); - contexts.getCurrentPersistenceContext(emf1, props1); + contexts.getCurrentPersistenceContext(emf1, props1, new AtomicLong(), Skeleton.newMock(DestroyCallback.class)); } @Test(expected=TransactionRequiredException.class) public void testGetNoTranSyncRegistry() { reg.setTransactionKey(""); - contexts.removeRegistry(null); - contexts.getCurrentPersistenceContext(emf1, props1); + contexts.removedService(ref, ref); + contexts.getCurrentPersistenceContext(emf1, props1, new AtomicLong(), Skeleton.newMock(DestroyCallback.class)); } } Added: incubator/aries/trunk/jpa/jpa-container-context/src/test/java/org/apache/aries/jpa/container/context/transaction/impl/TranSyncRegistryMock.java URL: http://svn.apache.org/viewvc/incubator/aries/trunk/jpa/jpa-container-context/src/test/java/org/apache/aries/jpa/container/context/transaction/impl/TranSyncRegistryMock.java?rev=982543&view=auto ============================================================================== --- incubator/aries/trunk/jpa/jpa-container-context/src/test/java/org/apache/aries/jpa/container/context/transaction/impl/TranSyncRegistryMock.java (added) +++ incubator/aries/trunk/jpa/jpa-container-context/src/test/java/org/apache/aries/jpa/container/context/transaction/impl/TranSyncRegistryMock.java Thu Aug 5 11:13:55 2010 @@ -0,0 +1,80 @@ +/** + * 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.aries.jpa.container.context.transaction.impl; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.transaction.Status; +import javax.transaction.Synchronization; + +public class TranSyncRegistryMock +{ + private String key; + + private Map> syncs = new HashMap>(); + + private Map> resources = new HashMap>(); + + public void setTransactionKey(String s) + { + key = s; + } + + public Object getTransactionKey() { + return key; + } + + public void registerInterposedSynchronization(Synchronization arg0) { + List list = syncs.get(key); + if(list == null) { + list = new ArrayList(); + syncs.put(key, list); + } + list.add(arg0); + } + + public Object getResource(Object o) { + Object toReturn = null; + Map map = resources.get(key); + if(map != null) + toReturn = map.get(o); + return toReturn; + } + + public void putResource(Object resourceKey, Object value) { + Map map = resources.get(key); + if(map == null) { + map = new HashMap(); + resources.put(key, map); + } + map.put(resourceKey, value); + } + + + public void afterCompletion(String s) + { + for(Synchronization sync : syncs.get(s)) + sync.afterCompletion(Status.STATUS_COMMITTED); + + resources.remove(s); + } +} \ No newline at end of file Modified: incubator/aries/trunk/jpa/jpa-container-itest/pom.xml URL: http://svn.apache.org/viewvc/incubator/aries/trunk/jpa/jpa-container-itest/pom.xml?rev=982543&r1=982542&r2=982543&view=diff ============================================================================== --- incubator/aries/trunk/jpa/jpa-container-itest/pom.xml (original) +++ incubator/aries/trunk/jpa/jpa-container-itest/pom.xml Thu Aug 5 11:13:55 2010 @@ -180,6 +180,46 @@ org.apache.aries.jpa.blueprint.itest.bundle test + + org.apache.aries.quiesce + org.apache.aries.quiesce.api + provided + + + org.apache.aries.transaction + org.apache.aries.transaction.manager + test + + + org.apache.aries.transaction + org.apache.aries.transaction.testds + test + + + org.apache.aries.jndi + org.apache.aries.jndi.api + test + + + org.apache.aries.jndi + org.apache.aries.jndi.core + test + + + org.apache.aries.jndi + org.apache.aries.jndi.url + test + + + org.apache.derby + derby + test + + + org.apache.servicemix.bundles + org.apache.servicemix.bundles.cglib + test +