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.keymanager.channel;
019    
020    import java.util.Collections;
021    import java.util.HashMap;
022    import java.util.HashSet;
023    import java.util.Map;
024    import java.util.Set;
025    
026    import org.cumulus4j.keymanager.SessionManager;
027    import org.cumulus4j.keymanager.back.shared.GetActiveEncryptionKeyRequest;
028    import org.cumulus4j.keymanager.back.shared.GetKeyRequest;
029    import org.cumulus4j.keymanager.back.shared.Request;
030    
031    /**
032     * <p>
033     * Manager for the communication channel between key manager and application server.
034     * </p>
035     * <p>
036     * The so-called "key manager channel" is - as shown in the document
037     * <a target="_blank" href="http://cumulus4j.org/1.2.0/documentation/deployment-scenarios.html">Deployment scenarios</a> - an
038     * HTTP(S) connection from the key-manager to the application server with an inverse request-response-cycle.
039     * This means, the application server sends a {@link org.cumulus4j.keymanager.back.shared.Request},
040     * the key manager handles it and then sends a {@link org.cumulus4j.keymanager.back.shared.Response} back.
041     * </p>
042     *
043     * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de
044     */
045    public class KeyManagerChannelManager
046    {
047            private SessionManager sessionManager;
048            private String appServerBaseURL;
049            private String keyManagerChannelURL;
050            private int desiredThreadCount;
051    
052            private Set<KeyManagerChannelListenerThread> listenerThreads = Collections.synchronizedSet(new HashSet<KeyManagerChannelListenerThread>());
053    
054            private static final Map<Class<? extends Request>, Class<? extends RequestHandler<?>>> requestClass2handlerClass;
055            static {
056                    Map<Class<? extends Request>, Class<? extends RequestHandler<?>>> m = new HashMap<Class<? extends Request>, Class<? extends RequestHandler<?>>>();
057                    m.put(GetKeyRequest.class, GetKeyRequestHandler.class);
058                    m.put(GetActiveEncryptionKeyRequest.class, GetActiveEncryptionKeyRequestHandler.class);
059                    requestClass2handlerClass = Collections.unmodifiableMap(m);
060            }
061    
062            /**
063             * Instantiate a <code>KeyManagerChannelManager</code>.
064             *
065             * @param sessionManager the {@link SessionManager} which
066             * @param appServerBaseURL the base-URL before the "/KeyManagerChannel" - e.g. if the REST URL of the KeyManagerChannel-service is
067             * "https://serverUsingCumulus4j.mydomain.org/org.cumulus4j.keymanager.back.webapp/KeyManagerChannel", then this must be
068             * "https://serverUsingCumulus4j.mydomain.org/org.cumulus4j.keymanager.back.webapp".
069             */
070            public KeyManagerChannelManager(SessionManager sessionManager, String appServerBaseURL)
071            {
072                    if (sessionManager == null)
073                            throw new IllegalArgumentException("sessionManager == null");
074    
075                    if (appServerBaseURL == null)
076                            throw new IllegalArgumentException("appServerBaseURL == null");
077    
078                    this.sessionManager = sessionManager;
079    
080                    this.appServerBaseURL = appServerBaseURL;
081    
082                    String s = appServerBaseURL.toString();
083                    if (!s.endsWith("/"))
084                            s += '/';
085    
086                    this.keyManagerChannelURL = s + "KeyManagerChannel";
087    
088                    setDesiredThreadCount(5); // TODO make this manage itself automatically according to load statistics
089            }
090    
091            /**
092             * Get the {@link SessionManager} that was passed in the constructor.
093             * @return the {@link SessionManager}.
094             */
095            public SessionManager getSessionManager() {
096                    return sessionManager;
097            }
098    
099            /**
100             * Get the base-URL before the "/KeyManagerChannel" - e.g. if the REST URL of the KeyManagerChannel-service is
101             * "https://serverUsingCumulus4j.mydomain.org/org.cumulus4j.keymanager.back.webapp/KeyManagerChannel", then this must be
102             * "https://serverUsingCumulus4j.mydomain.org/org.cumulus4j.keymanager.back.webapp".
103             * @return the base-URL before the "/KeyManagerChannel".
104             */
105            public String getAppServerBaseURL() {
106                    return appServerBaseURL;
107            }
108    
109            /**
110             * Get the complete URL to the <code>KeyManagerChannel</code>.
111             *
112             * @return the complete URL to the <code>KeyManagerChannel</code>.
113             */
114            public String getKeyManagerChannelURL() {
115                    return keyManagerChannelURL;
116            }
117    
118            /**
119             * <p>
120             * Set the quantity of {@link KeyManagerChannelListenerThread}s that should be running for this
121             * {@link KeyManagerChannelManager}.
122             * </p>
123             * <p>
124             * If the given <code>desiredThreadCount</code> is greater than
125             * the number of currently running threads, new threads are created. If the <code>desiredThreadCount</code>
126             * is less than the number of currently running threads, some of the threads will terminate themselves
127             * until the number of currently running threads matches the desired quantity.
128             * </p>
129             * @param desiredThreadCount the new quantity of {@link KeyManagerChannelListenerThread}s which should be
130             * active for this {@link KeyManagerChannelManager}.
131             * @see #getDesiredThreadCount()
132             */
133            public void setDesiredThreadCount(int desiredThreadCount) {
134                    this.desiredThreadCount = desiredThreadCount;
135                    while (listenerThreads.size() < desiredThreadCount) {
136                            KeyManagerChannelListenerThread thread = new KeyManagerChannelListenerThread(this);
137                            listenerThreads.add(thread);
138                            thread.start();
139                    }
140            }
141    
142            /**
143             * Get the quantity of {@link KeyManagerChannelListenerThread}s that should be running for this
144             * {@link KeyManagerChannelManager}.
145             * @return the quantity of {@link KeyManagerChannelListenerThread}s that should be active for this
146             * {@link KeyManagerChannelManager}.
147             * @see #setDesiredThreadCount(int)
148             */
149            public int getDesiredThreadCount() {
150                    return desiredThreadCount;
151            }
152    
153            /**
154             * <p>
155             * Unregister the given <code>thread</code>, if there are currently more threads running than desired.
156             * </p>
157             * <p>
158             * This method is called by a {@link KeyManagerChannelListenerThread} in its run-loop to determine, if the thread
159             * should terminate itself (see {@link #setDesiredThreadCount(int)}). If the method returns <code>true</code>,
160             * the thread will exit its {@link Thread#run() run()} method.
161             * </p>
162             * @param thread the thread.
163             * @return <code>true</code> if the thread was unregistered and thus must exit its <code>run()</code> method;
164             * <code>false</code> if the thread was not unregistered and should thus continue.
165             */
166            protected boolean unregisterThreadIfMoreThanDesiredThreadCount(KeyManagerChannelListenerThread thread)
167            {
168                    synchronized (listenerThreads) {
169                            if (listenerThreads.size() > desiredThreadCount) {
170                                    listenerThreads.remove(thread);
171                                    return true;
172                            }
173                            else
174                                    return false;
175                    }
176            }
177    
178            /**
179             * Get the appropriate {@link RequestHandler handler} for the given <code>request</code>.
180             *
181             * @param <R> the type of the <code>request</code>.
182             * @param request the request.
183             * @return the {@link RequestHandler} for the request.
184             * @throws InstantiationException if {@link Class#newInstance()} failed to create the handler instance.
185             * @throws IllegalAccessException if {@link Class#newInstance()} failed to create the handler instance.
186             */
187            protected <R extends Request> RequestHandler<R> getRequestHandler(R request)
188            throws InstantiationException, IllegalAccessException
189            {
190                    if (request == null)
191                            throw new IllegalArgumentException("request == null");
192    
193                    Class<? extends Request> requestClass = request.getClass();
194                    Class<? extends RequestHandler<?>> handlerClass = requestClass2handlerClass.get(requestClass);
195                    if (handlerClass == null)
196                            throw new IllegalStateException("There is no RequestHandler class registered for this requestClass: " + requestClass);
197    
198                    @SuppressWarnings("unchecked")
199                    RequestHandler<R> requestHandler = (RequestHandler<R>) handlerClass.newInstance();
200                    requestHandler.setKeyManagerChannelManager(this);
201                    return requestHandler;
202            }
203    }