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    }