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 }