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 }