Merge "fix bug 431"
[controller.git] / opendaylight / config / config-manager / src / main / java / org / opendaylight / controller / config / manager / impl / ConfigRegistryImpl.java
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.controller.config.manager.impl;
9
10 import org.opendaylight.controller.config.api.ConflictingVersionException;
11 import org.opendaylight.controller.config.api.ModuleIdentifier;
12 import org.opendaylight.controller.config.api.RuntimeBeanRegistratorAwareModule;
13 import org.opendaylight.controller.config.api.ServiceReferenceWritableRegistry;
14 import org.opendaylight.controller.config.api.ValidationException;
15 import org.opendaylight.controller.config.api.jmx.CommitStatus;
16 import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
17 import org.opendaylight.controller.config.manager.impl.dynamicmbean.DynamicReadableWrapper;
18 import org.opendaylight.controller.config.manager.impl.factoriesresolver.HierarchicalConfigMBeanFactoriesHolder;
19 import org.opendaylight.controller.config.manager.impl.factoriesresolver.ModuleFactoriesResolver;
20 import org.opendaylight.controller.config.manager.impl.jmx.BaseJMXRegistrator;
21 import org.opendaylight.controller.config.manager.impl.jmx.ModuleJMXRegistrator;
22 import org.opendaylight.controller.config.manager.impl.jmx.RootRuntimeBeanRegistratorImpl;
23 import org.opendaylight.controller.config.manager.impl.jmx.TransactionJMXRegistrator;
24 import org.opendaylight.controller.config.manager.impl.osgi.BeanToOsgiServiceManager;
25 import org.opendaylight.controller.config.manager.impl.osgi.BeanToOsgiServiceManager.OsgiRegistration;
26 import org.opendaylight.controller.config.manager.impl.util.LookupBeansUtil;
27 import org.opendaylight.controller.config.manager.impl.util.ModuleQNameUtil;
28 import org.opendaylight.controller.config.spi.Module;
29 import org.opendaylight.controller.config.spi.ModuleFactory;
30 import org.opendaylight.yangtools.yang.data.impl.codec.CodecRegistry;
31 import org.osgi.framework.BundleContext;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 import javax.annotation.concurrent.GuardedBy;
36 import javax.annotation.concurrent.NotThreadSafe;
37 import javax.annotation.concurrent.ThreadSafe;
38 import javax.management.InstanceAlreadyExistsException;
39 import javax.management.InstanceNotFoundException;
40 import javax.management.MBeanServer;
41 import javax.management.MBeanServerFactory;
42 import javax.management.ObjectName;
43 import java.util.ArrayList;
44 import java.util.Collection;
45 import java.util.Collections;
46 import java.util.HashMap;
47 import java.util.Iterator;
48 import java.util.LinkedList;
49 import java.util.List;
50 import java.util.Map;
51 import java.util.Map.Entry;
52 import java.util.Set;
53
54 /**
55  * Singleton that is responsible for creating and committing Config
56  * Transactions. It is registered in Platform MBean Server.
57  */
58 @ThreadSafe
59 public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBean {
60     private static final Logger logger = LoggerFactory.getLogger(ConfigRegistryImpl.class);
61
62     private final ModuleFactoriesResolver resolver;
63     private final MBeanServer configMBeanServer;
64     private final CodecRegistry codecRegistry;
65
66     @GuardedBy("this")
67     private long version = 0;
68     @GuardedBy("this")
69     private long versionCounter = 0;
70
71     /**
72      * Contains current configuration in form of {moduleName:{instanceName,read
73      * only module}} for copying state to new transaction. Each running module
74      * is present just once, no matter how many interfaces it exposes.
75      */
76     @GuardedBy("this")
77     private final ConfigHolder currentConfig = new ConfigHolder();
78
79     /**
80      * Will return true unless there was a transaction that succeeded during
81      * validation but failed in second phase of commit. In this case the server
82      * is unstable and its state is undefined.
83      */
84     @GuardedBy("this")
85     private boolean isHealthy = true;
86
87     /**
88      * Holds Map<transactionName, transactionController> and purges it each time
89      * its content is requested.
90      */
91     @GuardedBy("this")
92     private final TransactionsHolder transactionsHolder = new TransactionsHolder();
93
94     private final BaseJMXRegistrator baseJMXRegistrator;
95
96     private final BeanToOsgiServiceManager beanToOsgiServiceManager;
97
98     // internal jmx server for read only beans
99     private final MBeanServer registryMBeanServer;
100     // internal jmx server shared by all transactions
101     private final MBeanServer transactionsMBeanServer;
102
103     // Used for finding new factory instances for default module functionality
104     @GuardedBy("this")
105     private List<ModuleFactory> lastListOfFactories = Collections.emptyList();
106
107     @GuardedBy("this") // switched in every 2ndPC
108     private CloseableServiceReferenceReadableRegistry readableSRRegistry = ServiceReferenceRegistryImpl.createInitialSRLookupRegistry();
109
110     // constructor
111     public ConfigRegistryImpl(ModuleFactoriesResolver resolver,
112                               MBeanServer configMBeanServer, CodecRegistry codecRegistry) {
113         this(resolver, configMBeanServer,
114                 new BaseJMXRegistrator(configMBeanServer), codecRegistry);
115     }
116
117     // constructor
118     public ConfigRegistryImpl(ModuleFactoriesResolver resolver,
119                               MBeanServer configMBeanServer,
120                               BaseJMXRegistrator baseJMXRegistrator, CodecRegistry codecRegistry) {
121         this.resolver = resolver;
122         beanToOsgiServiceManager = new BeanToOsgiServiceManager();
123         this.configMBeanServer = configMBeanServer;
124         this.baseJMXRegistrator = baseJMXRegistrator;
125         this.codecRegistry = codecRegistry;
126         registryMBeanServer = MBeanServerFactory
127                 .createMBeanServer("ConfigRegistry" + configMBeanServer.getDefaultDomain());
128         transactionsMBeanServer = MBeanServerFactory
129                 .createMBeanServer("ConfigTransactions" + configMBeanServer.getDefaultDomain());
130     }
131
132     /**
133      * Create new {@link ConfigTransactionControllerImpl }
134      */
135     @Override
136     public synchronized ObjectName beginConfig() {
137         return beginConfig(false);
138     }
139
140     /**
141      * @param blankTransaction true if this transaction is created automatically by
142      *                         org.opendaylight.controller.config.manager.impl.osgi.BlankTransactionServiceTracker
143      */
144     public synchronized ObjectName beginConfig(boolean blankTransaction) {
145         return beginConfigInternal(blankTransaction).getControllerObjectName();
146     }
147
148     private synchronized ConfigTransactionControllerInternal beginConfigInternal(boolean blankTransaction) {
149         versionCounter++;
150         final String transactionName = "ConfigTransaction-" + version + "-" + versionCounter;
151
152         TransactionJMXRegistratorFactory factory = new TransactionJMXRegistratorFactory() {
153             @Override
154             public TransactionJMXRegistrator create() {
155                 return baseJMXRegistrator.createTransactionJMXRegistrator(transactionName);
156             }
157         };
158
159         Map<String, Map.Entry<ModuleFactory, BundleContext>> allCurrentFactories = Collections.unmodifiableMap(
160                 resolver.getAllFactories());
161         ConfigTransactionLookupRegistry txLookupRegistry = new ConfigTransactionLookupRegistry(new TransactionIdentifier(
162                 transactionName), factory, allCurrentFactories);
163         ServiceReferenceWritableRegistry writableRegistry = ServiceReferenceRegistryImpl.createSRWritableRegistry(
164                 readableSRRegistry, txLookupRegistry, allCurrentFactories);
165
166         ConfigTransactionControllerInternal transactionController = new ConfigTransactionControllerImpl(
167                 txLookupRegistry, version, codecRegistry,
168                 versionCounter, allCurrentFactories, transactionsMBeanServer,
169                 configMBeanServer, blankTransaction, writableRegistry);
170         try {
171             txLookupRegistry.registerMBean(transactionController, transactionController.getControllerObjectName());
172         } catch (InstanceAlreadyExistsException e) {
173             throw new IllegalStateException(e);
174         }
175         transactionController.copyExistingModulesAndProcessFactoryDiff(currentConfig.getEntries(), lastListOfFactories);
176         transactionsHolder.add(transactionName, transactionController);
177         return transactionController;
178     }
179
180     /**
181      * {@inheritDoc}
182      */
183     @Override
184     @SuppressWarnings("PMD.AvoidCatchingThrowable")
185     public synchronized CommitStatus commitConfig(ObjectName transactionControllerON)
186             throws ConflictingVersionException, ValidationException {
187         final String transactionName = ObjectNameUtil
188                 .getTransactionName(transactionControllerON);
189         logger.trace("About to commit {}. Current parentVersion: {}, versionCounter {}", transactionName, version, versionCounter);
190
191         // find ConfigTransactionController
192         Map<String, ConfigTransactionControllerInternal> transactions = transactionsHolder.getCurrentTransactions();
193         ConfigTransactionControllerInternal configTransactionController = transactions.get(transactionName);
194         if (configTransactionController == null) {
195             throw new IllegalArgumentException(String.format(
196                     "Transaction with name '%s' not found", transactionName));
197         }
198         // check optimistic lock
199         if (version != configTransactionController.getParentVersion()) {
200             throw new ConflictingVersionException(
201                     String.format(
202                             "Optimistic lock failed. Expected parent version %d, was %d",
203                             version,
204                             configTransactionController.getParentVersion()));
205         }
206         // optimistic lock ok
207
208         CommitInfo commitInfo = configTransactionController.validateBeforeCommitAndLockTransaction();
209         lastListOfFactories = Collections.unmodifiableList(configTransactionController.getCurrentlyRegisteredFactories());
210         // non recoverable from here:
211         try {
212             return secondPhaseCommit(
213                     configTransactionController, commitInfo);
214         } catch (Throwable t) { // some libs throw Errors: e.g.
215             // javax.xml.ws.spi.FactoryFinder$ConfigurationError
216             isHealthy = false;
217             logger.error("Configuration Transaction failed on 2PC, server is unhealthy", t);
218             if (t instanceof RuntimeException) {
219                 throw (RuntimeException) t;
220             } else if (t instanceof Error) {
221                 throw (Error) t;
222             } else {
223                 throw new IllegalStateException(t);
224             }
225         }
226     }
227
228     private CommitStatus secondPhaseCommit(ConfigTransactionControllerInternal configTransactionController,
229                                            CommitInfo commitInfo) {
230
231         // close instances which were destroyed by the user, including
232         // (hopefully) runtime beans
233         for (DestroyedModule toBeDestroyed : commitInfo
234                 .getDestroyedFromPreviousTransactions()) {
235             toBeDestroyed.close(); // closes instance (which should close
236             // runtime jmx registrator),
237             // also closes osgi registration and ModuleJMXRegistrator
238             // registration
239             currentConfig.remove(toBeDestroyed.getIdentifier());
240         }
241
242         // set RuntimeBeanRegistrators on beans implementing
243         // RuntimeBeanRegistratorAwareModule, each module
244         // should have exactly one runtime jmx registrator.
245         Map<ModuleIdentifier, RootRuntimeBeanRegistratorImpl> runtimeRegistrators = new HashMap<>();
246         for (ModuleInternalTransactionalInfo entry : commitInfo.getCommitted()
247                 .values()) {
248             RootRuntimeBeanRegistratorImpl runtimeBeanRegistrator;
249             if (entry.hasOldModule() == false) {
250                 runtimeBeanRegistrator = baseJMXRegistrator
251                         .createRuntimeBeanRegistrator(entry.getIdentifier());
252             } else {
253                 // reuse old JMX registrator
254                 runtimeBeanRegistrator = entry.getOldInternalInfo()
255                         .getRuntimeBeanRegistrator();
256             }
257             // set runtime jmx registrator if required
258             Module module = entry.getModule();
259             if (module instanceof RuntimeBeanRegistratorAwareModule) {
260                 ((RuntimeBeanRegistratorAwareModule) module)
261                         .setRuntimeBeanRegistrator(runtimeBeanRegistrator);
262             }
263             // save it to info so it is accessible afterwards
264             runtimeRegistrators.put(entry.getIdentifier(), runtimeBeanRegistrator);
265         }
266
267         // can register runtime beans
268         List<ModuleIdentifier> orderedModuleIdentifiers = configTransactionController
269                 .secondPhaseCommit();
270
271         // copy configuration to read only mode
272         List<ObjectName> newInstances = new LinkedList<>();
273         List<ObjectName> reusedInstances = new LinkedList<>();
274         List<ObjectName> recreatedInstances = new LinkedList<>();
275
276         Map<Module, ModuleInternalInfo> newConfigEntries = new HashMap<>();
277
278         int orderingIdx = 0;
279         for (ModuleIdentifier moduleIdentifier : orderedModuleIdentifiers) {
280             ModuleInternalTransactionalInfo entry = commitInfo.getCommitted()
281                     .get(moduleIdentifier);
282             if (entry == null) {
283                 throw new NullPointerException("Module not found "
284                         + moduleIdentifier);
285             }
286             Module module = entry.getModule();
287             ObjectName primaryReadOnlyON = ObjectNameUtil
288                     .createReadOnlyModuleON(moduleIdentifier);
289
290             // determine if current instance was recreated or reused or is new
291
292             // rules for closing resources:
293             // osgi registration - will be reused if possible.
294             // module jmx registration - will be (re)created every time, needs
295             // to be closed here
296             // runtime jmx registration - should be taken care of by module
297             // itself
298             // instance - is closed only if it was destroyed
299             ModuleJMXRegistrator newModuleJMXRegistrator = baseJMXRegistrator
300                     .createModuleJMXRegistrator();
301
302             OsgiRegistration osgiRegistration = null;
303             if (entry.hasOldModule()) {
304                 ModuleInternalInfo oldInternalInfo = entry.getOldInternalInfo();
305                 DynamicReadableWrapper oldReadableConfigBean = oldInternalInfo.getReadableModule();
306                 currentConfig.remove(entry.getIdentifier());
307
308                 // test if old instance == new instance
309                 if (oldReadableConfigBean.getInstance().equals(module.getInstance())) {
310                     // reused old instance:
311                     // wrap in readable dynamic mbean
312                     reusedInstances.add(primaryReadOnlyON);
313                     osgiRegistration = oldInternalInfo.getOsgiRegistration();
314                 } else {
315                     // recreated instance:
316                     // it is responsibility of module to call the old instance -
317                     // we just need to unregister configbean
318                     recreatedInstances.add(primaryReadOnlyON);
319
320                     // close old osgi registration
321                     oldInternalInfo.getOsgiRegistration().close();
322                 }
323
324                 // close old module jmx registrator
325                 oldInternalInfo.getModuleJMXRegistrator().close();
326             } else {
327                 // new instance:
328                 // wrap in readable dynamic mbean
329                 newInstances.add(primaryReadOnlyON);
330             }
331
332             DynamicReadableWrapper newReadableConfigBean = new DynamicReadableWrapper(
333                     module, module.getInstance(), moduleIdentifier,
334                     registryMBeanServer, configMBeanServer);
335
336             // register to JMX
337             try {
338                 newModuleJMXRegistrator.registerMBean(newReadableConfigBean,
339                         primaryReadOnlyON);
340             } catch (InstanceAlreadyExistsException e) {
341                 throw new IllegalStateException(e);
342             }
343
344             // register to OSGi
345             if (osgiRegistration == null) {
346                 ModuleFactory moduleFactory = entry.getModuleFactory();
347                 if (moduleFactory != null) {
348                     BundleContext bc = configTransactionController.
349                             getModuleFactoryBundleContext(moduleFactory.getImplementationName());
350                     osgiRegistration = beanToOsgiServiceManager.registerToOsgi(module.getClass(),
351                             newReadableConfigBean.getInstance(), entry.getIdentifier(), bc);
352                 } else {
353                     throw new NullPointerException(entry.getIdentifier().getFactoryName() + " ModuleFactory not found.");
354                 }
355
356             }
357
358             RootRuntimeBeanRegistratorImpl runtimeBeanRegistrator = runtimeRegistrators
359                     .get(entry.getIdentifier());
360             ModuleInternalInfo newInfo = new ModuleInternalInfo(
361                     entry.getIdentifier(), newReadableConfigBean, osgiRegistration,
362                     runtimeBeanRegistrator, newModuleJMXRegistrator,
363                     orderingIdx, entry.isDefaultBean());
364
365             newConfigEntries.put(module, newInfo);
366             orderingIdx++;
367         }
368         currentConfig.addAll(newConfigEntries.values());
369
370         // update version
371         version = configTransactionController.getVersion();
372
373         // switch readable Service Reference Registry
374         readableSRRegistry.close();
375         readableSRRegistry = ServiceReferenceRegistryImpl.createSRReadableRegistry(
376                 configTransactionController.getWritableRegistry(), this, baseJMXRegistrator);
377
378         return new CommitStatus(newInstances, reusedInstances,
379                 recreatedInstances);
380     }
381
382     /**
383      * {@inheritDoc}
384      */
385     @Override
386     public synchronized List<ObjectName> getOpenConfigs() {
387         Map<String, ConfigTransactionControllerInternal> transactions = transactionsHolder
388                 .getCurrentTransactions();
389         List<ObjectName> result = new ArrayList<>(transactions.size());
390         for (ConfigTransactionControllerInternal configTransactionController : transactions
391                 .values()) {
392             result.add(configTransactionController.getControllerObjectName());
393         }
394         return result;
395     }
396
397     /**
398      * Abort open transactions and unregister read only modules. Since this
399      * class is not responsible for registering itself under
400      * {@link org.opendaylight.controller.config.api.ConfigRegistry#OBJECT_NAME}, it will not unregister itself
401      * here.
402      */
403     @Override
404     public synchronized void close() {
405         // abort transactions
406         Map<String, ConfigTransactionControllerInternal> transactions = transactionsHolder
407                 .getCurrentTransactions();
408         for (ConfigTransactionControllerInternal configTransactionController : transactions
409                 .values()) {
410             try {
411                 configTransactionController.abortConfig();
412             } catch (RuntimeException e) {
413                 logger.warn("Ignoring exception while aborting {}",
414                         configTransactionController, e);
415             }
416         }
417
418         // destroy all live objects one after another in order of the dependency
419         // hierarchy
420         List<DestroyedModule> destroyedModules = currentConfig
421                 .getModulesToBeDestroyed();
422         for (DestroyedModule destroyedModule : destroyedModules) {
423             destroyedModule.close();
424         }
425         // unregister MBeans that failed to unregister properly
426         baseJMXRegistrator.close();
427         // remove jmx servers
428         MBeanServerFactory.releaseMBeanServer(registryMBeanServer);
429         MBeanServerFactory.releaseMBeanServer(transactionsMBeanServer);
430
431     }
432
433     /**
434      * {@inheritDoc}
435      */
436     @Override
437     public long getVersion() {
438         return version;
439     }
440
441     /**
442      * {@inheritDoc}
443      */
444     @Override
445     public Set<String> getAvailableModuleNames() {
446         return new HierarchicalConfigMBeanFactoriesHolder(
447                 resolver.getAllFactories()).getModuleNames();
448     }
449
450     /**
451      * {@inheritDoc}
452      */
453     @Override
454     public boolean isHealthy() {
455         return isHealthy;
456     }
457
458     // filtering methods
459
460     /**
461      * {@inheritDoc}
462      */
463     @Override
464     public Set<ObjectName> lookupConfigBeans() {
465         return lookupConfigBeans("*", "*");
466     }
467
468     /**
469      * {@inheritDoc}
470      */
471     @Override
472     public Set<ObjectName> lookupConfigBeans(String moduleName) {
473         return lookupConfigBeans(moduleName, "*");
474     }
475
476     /**
477      * {@inheritDoc}
478      */
479     @Override
480     public ObjectName lookupConfigBean(String moduleName, String instanceName)
481             throws InstanceNotFoundException {
482         return LookupBeansUtil.lookupConfigBean(this, moduleName, instanceName);
483     }
484
485     /**
486      * {@inheritDoc}
487      */
488     @Override
489     public Set<ObjectName> lookupConfigBeans(String moduleName,
490                                              String instanceName) {
491         ObjectName namePattern = ObjectNameUtil.createModulePattern(moduleName,
492                 instanceName);
493         return baseJMXRegistrator.queryNames(namePattern, null);
494     }
495
496     /**
497      * {@inheritDoc}
498      */
499     @Override
500     public Set<ObjectName> lookupRuntimeBeans() {
501         return lookupRuntimeBeans("*", "*");
502     }
503
504     /**
505      * {@inheritDoc}
506      */
507     @Override
508     public Set<ObjectName> lookupRuntimeBeans(String moduleName,
509                                               String instanceName) {
510         if (moduleName == null) {
511             moduleName = "*";
512         }
513         if (instanceName == null) {
514             instanceName = "*";
515         }
516         ObjectName namePattern = ObjectNameUtil.createRuntimeBeanPattern(
517                 moduleName, instanceName);
518         return baseJMXRegistrator.queryNames(namePattern, null);
519     }
520
521     @Override
522     public void checkConfigBeanExists(ObjectName objectName) throws InstanceNotFoundException {
523         ObjectNameUtil.checkDomain(objectName);
524         ObjectNameUtil.checkType(objectName, ObjectNameUtil.TYPE_MODULE);
525         String transactionName = ObjectNameUtil.getTransactionName(objectName);
526         if (transactionName != null) {
527             throw new IllegalArgumentException("Transaction attribute not supported in registry, wrong ObjectName: " + objectName);
528         }
529         // make sure exactly one match is found:
530         LookupBeansUtil.lookupConfigBean(this, ObjectNameUtil.getFactoryName(objectName), ObjectNameUtil.getInstanceName(objectName));
531     }
532
533     // service reference functionality:
534     @Override
535     public synchronized ObjectName lookupConfigBeanByServiceInterfaceName(String serviceInterfaceQName, String refName) {
536         return readableSRRegistry.lookupConfigBeanByServiceInterfaceName(serviceInterfaceQName, refName);
537     }
538
539     @Override
540     public synchronized Map<String, Map<String, ObjectName>> getServiceMapping() {
541         return readableSRRegistry.getServiceMapping();
542     }
543
544     @Override
545     public synchronized Map<String, ObjectName> lookupServiceReferencesByServiceInterfaceName(String serviceInterfaceQName) {
546         return readableSRRegistry.lookupServiceReferencesByServiceInterfaceName(serviceInterfaceQName);
547     }
548
549     @Override
550     public synchronized Set<String> lookupServiceInterfaceNames(ObjectName objectName) throws InstanceNotFoundException {
551         return readableSRRegistry.lookupServiceInterfaceNames(objectName);
552     }
553
554     @Override
555     public synchronized String getServiceInterfaceName(String namespace, String localName) {
556         return readableSRRegistry.getServiceInterfaceName(namespace, localName);
557     }
558
559     @Override
560     public void checkServiceReferenceExists(ObjectName objectName) throws InstanceNotFoundException {
561         readableSRRegistry.checkServiceReferenceExists(objectName);
562     }
563
564     @Override
565     public ObjectName getServiceReference(String serviceInterfaceQName, String refName) throws InstanceNotFoundException {
566         return readableSRRegistry.getServiceReference(serviceInterfaceQName, refName);
567     }
568
569     @Override
570     public Set<String> getAvailableModuleFactoryQNames() {
571         return ModuleQNameUtil.getQNames(resolver.getAllFactories());
572     }
573
574     @Override
575     public String toString() {
576         return "ConfigRegistryImpl{" +
577                 "versionCounter=" + versionCounter +
578                 ", version=" + version +
579                 '}';
580     }
581 }
582
583 /**
584  * Holds currently running modules
585  */
586 @NotThreadSafe
587 class ConfigHolder {
588     private final Map<ModuleIdentifier, ModuleInternalInfo> currentConfig = new HashMap<>();
589
590     /**
591      * Add all modules to the internal map. Also add service instance to OSGi
592      * Service Registry.
593      */
594     public void addAll(Collection<ModuleInternalInfo> configInfos) {
595         if (currentConfig.size() > 0) {
596             throw new IllegalStateException(
597                     "Error - some config entries were not removed: "
598                             + currentConfig);
599         }
600         for (ModuleInternalInfo configInfo : configInfos) {
601             add(configInfo);
602         }
603     }
604
605     private void add(ModuleInternalInfo configInfo) {
606         ModuleInternalInfo oldValue = currentConfig.put(configInfo.getIdentifier(),
607                 configInfo);
608         if (oldValue != null) {
609             throw new IllegalStateException(
610                     "Cannot overwrite module with same name:"
611                             + configInfo.getIdentifier() + ":" + configInfo);
612         }
613     }
614
615     /**
616      * Remove entry from current config.
617      */
618     public void remove(ModuleIdentifier name) {
619         ModuleInternalInfo removed = currentConfig.remove(name);
620         if (removed == null) {
621             throw new IllegalStateException(
622                     "Cannot remove from ConfigHolder - name not found:" + name);
623         }
624     }
625
626     public Collection<ModuleInternalInfo> getEntries() {
627         return currentConfig.values();
628     }
629
630     public List<DestroyedModule> getModulesToBeDestroyed() {
631         List<DestroyedModule> result = new ArrayList<>();
632         for (ModuleInternalInfo moduleInternalInfo : getEntries()) {
633             result.add(moduleInternalInfo.toDestroyedModule());
634         }
635         Collections.sort(result);
636         return result;
637     }
638
639
640 }
641
642 /**
643  * Holds Map<transactionName, transactionController> and purges it each time its
644  * content is requested.
645  */
646 @NotThreadSafe
647 class TransactionsHolder {
648     /**
649      * This map keeps transaction names and
650      * {@link ConfigTransactionControllerInternal} instances, because platform
651      * MBeanServer transforms mbeans into another representation. Map is cleaned
652      * every time current transactions are requested.
653      */
654     @GuardedBy("ConfigRegistryImpl.this")
655     private final Map<String /* transactionName */, ConfigTransactionControllerInternal> transactions = new HashMap<>();
656
657     /**
658      * Can only be called from within synchronized method.
659      */
660     public void add(String transactionName,
661                     ConfigTransactionControllerInternal transactionController) {
662         Object oldValue = transactions.put(transactionName,
663                 transactionController);
664         if (oldValue != null) {
665             throw new IllegalStateException(
666                     "Error: two transactions with same name");
667         }
668     }
669
670     /**
671      * Purges closed transactions from transactions map. Can only be called from
672      * within synchronized method. Calling this method more than once within the
673      * method can modify the resulting map that was obtained in previous calls.
674      *
675      * @return current view on transactions map.
676      */
677     public Map<String, ConfigTransactionControllerInternal> getCurrentTransactions() {
678         // first, remove closed transaction
679         for (Iterator<Entry<String, ConfigTransactionControllerInternal>> it = transactions
680                 .entrySet().iterator(); it.hasNext(); ) {
681             Entry<String, ConfigTransactionControllerInternal> entry = it
682                     .next();
683             if (entry.getValue().isClosed()) {
684                 it.remove();
685             }
686         }
687         return Collections.unmodifiableMap(transactions);
688     }
689 }