001    /*
002     * Cumulus4j - Securing your data in the cloud - http://cumulus4j.org
003     * Copyright (C) 2011 NightLabs Consulting GmbH
004     *
005     * This program is free software: you can redistribute it and/or modify
006     * it under the terms of the GNU Affero General Public License as
007     * published by the Free Software Foundation, either version 3 of the
008     * License, or (at your option) any later version.
009     *
010     * This program is distributed in the hope that it will be useful,
011     * but WITHOUT ANY WARRANTY; without even the implied warranty of
012     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013     * GNU Affero General Public License for more details.
014     *
015     * You should have received a copy of the GNU Affero General Public License
016     * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017     */
018    package org.cumulus4j.store.crypto;
019    
020    import java.lang.ref.WeakReference;
021    import java.util.HashMap;
022    import java.util.Map;
023    import java.util.WeakHashMap;
024    
025    import org.datanucleus.NucleusContext;
026    import org.slf4j.Logger;
027    import org.slf4j.LoggerFactory;
028    
029    /**
030     * <p>
031     * Registry holding instances of {@link CryptoManager}.
032     * </p>
033     * <p>
034     * There is one JVM-singleton-instance of {@link CryptoManagerRegistry} per {@link NucleusContext}.
035     * Since it is held in a {@link WeakHashMap}, a <code>CryptoManagerRegistry</code> will be garbage-collected
036     * when the corresponding <code>NucleusContext</code> is "forgotten".
037     * </p>
038     *
039     * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de
040     */
041    public class CryptoManagerRegistry
042    {
043            private static final Logger logger = LoggerFactory.getLogger(CryptoManagerRegistry.class);
044            private static Map<NucleusContext, CryptoManagerRegistry> nucleusContext2sharedInstance = new WeakHashMap<NucleusContext, CryptoManagerRegistry>();
045    
046            /**
047             * <p>
048             * Get the {@link CryptoManagerRegistry} corresponding to a given {@link NucleusContext}.
049             * </p>
050             * <p>
051             * If there is no registry known for the given <code>NucleusContext</code>, yet, it will be created and
052             * associated to this context. If this method is later on called again for the same <code>NucleusContext</code>,
053             * the same <code>CryptoManagerRegistry</code> will be returned.
054             * </p>
055             * <p>
056             * This method is thread-safe.
057             * </p>
058             *
059             * @param nucleusContext the <code>NucleusContext</code> for which to get the <code>CryptoManagerRegistry</code>.
060             * @return the <code>CryptoManagerRegistry</code> associated to the given <code>NucleusContext</code>; never <code>null</code>.
061             */
062            public static CryptoManagerRegistry sharedInstance(NucleusContext nucleusContext)
063            {
064                    synchronized (nucleusContext2sharedInstance) {
065                            CryptoManagerRegistry registry = nucleusContext2sharedInstance.get(nucleusContext);
066                            if (registry == null) {
067                                    registry = new CryptoManagerRegistry(nucleusContext);
068                                    nucleusContext2sharedInstance.put(nucleusContext, registry);
069                            }
070                            return registry;
071                    }
072            }
073    
074            private WeakReference<NucleusContext> nucleusContextRef;
075    
076            private Map<String, CryptoManager> id2keyManager = new HashMap<String, CryptoManager>();
077    
078            private CryptoManagerRegistry(NucleusContext nucleusContext)
079            {
080                    if (nucleusContext == null)
081                            throw new IllegalArgumentException("nucleusContext == null");
082    
083                    this.nucleusContextRef = new WeakReference<NucleusContext>(nucleusContext);
084            }
085    
086            /**
087             * <p>
088             * Get a {@link CryptoManager} for the specified <code>cryptoManagerID</code>.
089             * </p>
090             * <p>
091             * Within the context of one <code>CryptoManagerRegistry</code> instance, this method will always
092             * return the same instance of <code>CryptoManager</code> for a certain <code>cryptoManagerID</code>.
093             * In other words, there is exactly one <code>CryptoManager</code> instance for each unique combination
094             * of {@link NucleusContext} and <code>cryptoManagerID</code>.
095             * </p>
096             * <p>
097             * This method is thread-safe.
098             * </p>
099             *
100             * @param cryptoManagerID the identifier used in the extension-declaration (in the <code>plugin.xml</code>).
101             * @return the {@link CryptoManager} for the specified <code>cryptoManagerID</code>; never <code>null</code>.
102             * @throws UnknownCryptoManagerIDException if there is no {@link CryptoManager} registered for the given identifier.
103             */
104            public CryptoManager getCryptoManager(String cryptoManagerID)
105            throws UnknownCryptoManagerIDException
106            {
107                    synchronized (id2keyManager) {
108                            CryptoManager cryptoManager = id2keyManager.get(cryptoManagerID);
109                            if (cryptoManager == null) {
110                                    cryptoManager = createCryptoManager(cryptoManagerID);
111                                    id2keyManager.put(cryptoManagerID, cryptoManager);
112                            }
113                            return cryptoManager;
114                    }
115            }
116    
117            /**
118             * <p>
119             * Get the {@link NucleusContext} for which this <code>CryptoManagerRegistry</code>
120             * has been created.
121             * </p>
122             * <p>
123             * This method returns <code>null</code>, if the
124             * <code>NucleusContext</code> has already been garbage-collected (the reference
125             * is kept as a {@link WeakReference}).
126             * </p>
127             * <p>
128             * <b>Important:</b> Hold the result of this method only in a stack variable (i.e. scope = method)
129             * or a {@link WeakReference}! Otherwise you run the risk of a memory leak!!!
130             * </p>
131             * @return the {@link NucleusContext} or <code>null</code>, if it was already garbage-collected.
132             * It is never <code>null</code> as long as the <code>NucleusContext</code> is still valid
133             * (i.e. not garbage-collected).
134             */
135            public NucleusContext getNucleusContext() {
136                    return nucleusContextRef.get();
137            }
138    
139            private CryptoManager createCryptoManager(String cryptoManagerID)
140            throws UnknownCryptoManagerIDException
141            {
142                    CryptoManager cryptoManager;
143                    try {
144                            NucleusContext nucleusContext = getNucleusContext();
145    
146                            cryptoManager = (CryptoManager) nucleusContext.getPluginManager().createExecutableExtension(
147                                            "org.cumulus4j.store.crypto_manager",
148                                            "crypto-manager-id", cryptoManagerID,
149                                            "class",
150                                            null, null
151                            );
152                    } catch (Exception e) {
153                            logger.error("Could not create CryptoManager from extension: " + e, e);
154                            throw new RuntimeException(e);
155                    }
156    
157                    if (cryptoManager == null)
158                            throw new UnknownCryptoManagerIDException(cryptoManagerID);
159    
160                    cryptoManager.setCryptoManagerRegistry(this);
161                    cryptoManager.setCryptoManagerID(cryptoManagerID);
162    
163                    return cryptoManager;
164            }
165    }