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.keymanager.messagebroker;
019
020 import java.util.concurrent.TimeoutException;
021
022 import org.cumulus4j.keymanager.back.shared.ErrorResponse;
023 import org.cumulus4j.keymanager.back.shared.NullResponse;
024 import org.cumulus4j.keymanager.back.shared.Request;
025 import org.cumulus4j.keymanager.back.shared.Response;
026 import org.cumulus4j.store.crypto.keymanager.rest.ErrorResponseException;
027 import org.slf4j.Logger;
028 import org.slf4j.LoggerFactory;
029
030 /**
031 * Abstract super-class to be subclassed by {@link MessageBroker} implementations.
032 * It is urgently recommended that <code>MessageBroker</code> implementations do not
033 * directly implement the <code>MessageBroker</code> interface, but instead subclass this abstract class.
034 *
035 * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de
036 */
037 public abstract class AbstractMessageBroker
038 implements MessageBroker
039 {
040 private static final Logger logger = LoggerFactory.getLogger(AbstractMessageBroker.class);
041
042 private long queryTimeoutMSec = -1;
043
044 private long pollRequestTimeout = -1;
045
046 @Override
047 public long getQueryTimeout()
048 {
049 if (queryTimeoutMSec < 0) {
050 String property = System.getProperty(SYSTEM_PROPERTY_QUERY_TIMEOUT);
051 long timeout = -1;
052 if (property != null && !property.isEmpty()) {
053 try {
054 timeout = Long.parseLong(property);
055 } catch (NumberFormatException x) {
056 logger.warn("Value \"{}\" of system property '{}' is not valid, because it cannot be parsed as number!", property, SYSTEM_PROPERTY_QUERY_TIMEOUT);
057 }
058 if (timeout < 0)
059 logger.warn("Value \"{}\" of system property '{}' is not valid, because it is less than 0!", property, SYSTEM_PROPERTY_QUERY_TIMEOUT);
060 else {
061 logger.info("System property '{}' is specified with value {}.", SYSTEM_PROPERTY_QUERY_TIMEOUT, timeout);
062 queryTimeoutMSec = timeout;
063 }
064 }
065
066 if (queryTimeoutMSec < 0) {
067 timeout = 5L * 60L * 1000L;
068 queryTimeoutMSec = timeout;
069 logger.info("System property '{}' is not specified; using default value {}.", SYSTEM_PROPERTY_QUERY_TIMEOUT, timeout);
070 }
071 }
072
073 return queryTimeoutMSec;
074 }
075
076 @Override
077 public long getPollRequestTimeout()
078 {
079 if (pollRequestTimeout < 0) {
080 String property = System.getProperty(SYSTEM_PROPERTY_POLL_REQUEST_TIMEOUT);
081 long timeout = -1;
082 if (property != null && !property.isEmpty()) {
083 try {
084 timeout = Long.parseLong(property);
085 } catch (NumberFormatException x) {
086 logger.warn("Value \"{}\" of system property '{}' is not valid, because it cannot be parsed as number!", property, SYSTEM_PROPERTY_POLL_REQUEST_TIMEOUT);
087 }
088 if (timeout < 0)
089 logger.warn("Value \"{}\" of system property '{}' is not valid, because it is less than 0!", property, SYSTEM_PROPERTY_POLL_REQUEST_TIMEOUT);
090 else {
091 logger.info("System property '{}' is specified with value {}.", SYSTEM_PROPERTY_POLL_REQUEST_TIMEOUT, timeout);
092 pollRequestTimeout = timeout;
093 }
094 }
095
096 if (pollRequestTimeout < 0) {
097 timeout = 1L * 60L * 1000L;
098 pollRequestTimeout = timeout;
099 logger.info("System property '{}' is not specified; using default value {}.", SYSTEM_PROPERTY_POLL_REQUEST_TIMEOUT, timeout);
100 }
101 }
102
103 return pollRequestTimeout;
104 }
105
106 // public static void main(String[] args)
107 // throws Exception
108 // {
109 // MessageBroker mb = new MessageBrokerPMF();
110 // mb.query(null, new GetKeyRequest());
111 // }
112
113 @Override
114 public final <R extends Response> R query(Class<R> responseClass, Request request)
115 throws TimeoutException, ErrorResponseException
116 {
117 Class<? extends Response> rc = responseClass;
118 if (rc == null)
119 rc = NullResponse.class;
120
121 if (request == null)
122 throw new IllegalArgumentException("request == null");
123
124 Response response = _query(rc, request);
125
126 if (response == null) // the implementation obviously already unmasked null somehow => directly returning it.
127 return null;
128
129 // A NullResponse which has a requestID assigned is forwarded to the requester and must be transformed into null here.
130 if (response instanceof NullResponse)
131 return null;
132
133 if (response instanceof ErrorResponse)
134 throw new ErrorResponseException((ErrorResponse)response);
135
136 if (responseClass == null) {
137 if (logger.isDebugEnabled()) {
138 Exception x = new Exception("StackTrace");
139 logger.warn("query: Caller passed responseClass=null, i.e. does not expect a result, but the server sent one, which we discard (we return null nevertheless). Here's the response we got: " + response, x);
140 }
141 else
142 logger.warn("query: Caller passed responseClass=null, i.e. does not expect a result, but the server sent one, which we discard (we return null nevertheless). Enable DEBUG logging to get a stack trace. Here's the response we got: {}", response);
143
144 return null;
145 }
146
147 try {
148 return responseClass.cast(response);
149 } catch (ClassCastException x) { // this exception has no nice message (according to source code), hence we throw our own below.
150 throw new ClassCastException("Expected a response of type " + responseClass + " but got an instance of " + response.getClass().getName() + "!");
151 }
152 }
153
154 /**
155 * Delegate of the {@link #query(Class, Request)} method. Subclasses should implement this method instead of <code>query(...)</code>.
156 *
157 * @param responseClass the type of the expected response; can be null, if you expect to receive null (i.e. you pass a "void" request).
158 * @param request the request to be sent to the key-manager.
159 * @return the response from the key-manager. Will be <code>null</code>, if the key-manager replied with a {@link NullResponse}.
160 * @throws TimeoutException if the request was not replied within the {@link #SYSTEM_PROPERTY_QUERY_TIMEOUT query-timeout}.
161 * @throws ErrorResponseException if the key-manager (either running embedded on the remote client or
162 * in a separate key-server) sent an {@link ErrorResponse}.
163 */
164 protected abstract Response _query(Class<? extends Response> responseClass, Request request)
165 throws TimeoutException, ErrorResponseException;
166
167 @Override
168 public final Request pollRequest(String cryptoSessionIDPrefix)
169 {
170 if (cryptoSessionIDPrefix == null)
171 throw new IllegalArgumentException("cryptoSessionIDPrefix == null");
172
173 return _pollRequest(cryptoSessionIDPrefix);
174 }
175
176 /**
177 * Delegate of the {@link #pollRequest(String)} method. Subclasses should implement this method instead of <code>pollRequest(...)</code>.
178 *
179 * @param cryptoSessionIDPrefix usually, every key-manager uses the same prefix for
180 * all crypto-sessions. Thus, this prefix is used to efficiently route requests to
181 * the right key-manager.
182 * @return the next request waiting for processing and fitting to the given <code>cryptoSessionIDPrefix</code>
183 * or <code>null</code>, if no such request pops up in the to-do-queue within the timeout.
184 */
185 protected abstract Request _pollRequest(String cryptoSessionIDPrefix);
186
187 @Override
188 public final void pushResponse(Response response)
189 {
190 if (response == null)
191 throw new IllegalArgumentException("response == null");
192
193 if (response.getRequestID() == null)
194 throw new IllegalArgumentException("response.requestID == null");
195
196 _pushResponse(response);
197 }
198
199 /**
200 * Delegate of the {@link #pushResponse(Response)} method. Subclasses should implement this method instead of <code>pushResponse(...)</code>.
201 *
202 * @param response the response answering a previous {@link Request} enqueued by {@link #query(Class, Request)}.
203 */
204 protected abstract void _pushResponse(Response response);
205
206 // @Override
207 // public ActiveKeyManagerChannelRegistration registerActiveKeyManagerChannel(String cryptoSessionIDPrefix, String internalKeyManagerChannelURL)
208 // {
209 // // no-op
210 // return null;
211 // }
212 //
213 // @Override
214 // public void unregisterActiveKeyManagerChannel(ActiveKeyManagerChannelRegistration registration)
215 // {
216 // // no-op
217 // }
218 }