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.io.BufferedReader;
021 import java.io.InputStream;
022 import java.io.InputStreamReader;
023 import java.io.StringWriter;
024 import java.net.URI;
025 import java.net.URL;
026
027 import javax.ws.rs.core.MediaType;
028
029 import org.cumulus4j.keymanager.back.shared.ErrorResponse;
030 import org.cumulus4j.keymanager.back.shared.JAXBContextResolver;
031 import org.cumulus4j.keymanager.back.shared.NullResponse;
032 import org.cumulus4j.keymanager.back.shared.Request;
033 import org.cumulus4j.keymanager.back.shared.Response;
034 import org.slf4j.Logger;
035 import org.slf4j.LoggerFactory;
036
037 import com.sun.jersey.api.client.Client;
038 import com.sun.jersey.api.client.ClientResponse;
039 import com.sun.jersey.api.client.UniformInterfaceException;
040 import com.sun.jersey.api.client.WebResource;
041 import com.sun.jersey.api.client.config.ClientConfig;
042 import com.sun.jersey.api.client.config.DefaultClientConfig;
043
044 /**
045 * <p>
046 * Thread that listens to incoming {@link Request}s and processes them.
047 * </p>
048 *
049 * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de
050 */
051 public class KeyManagerChannelListenerThread
052 extends Thread
053 {
054 private static final Logger logger = LoggerFactory.getLogger(KeyManagerChannelListenerThread.class);
055 private KeyManagerChannelManager keyManagerChannelManager;
056
057 /**
058 * Instantiate a new listener thread.
059 * @param keyManagerChannelManager the manager which instantiates this thread and manages
060 * the {@link RequestHandler}s to dispatch the incoming requests to.
061 */
062 public KeyManagerChannelListenerThread(KeyManagerChannelManager keyManagerChannelManager)
063 {
064 if (keyManagerChannelManager == null)
065 throw new IllegalArgumentException("keyManagerChannelManager == null");
066
067 this.keyManagerChannelManager = keyManagerChannelManager;
068 setDaemon(true);
069 }
070
071 private volatile boolean interruptForced;
072
073 @Override
074 public void interrupt() {
075 interruptForced = true;
076 super.interrupt();
077 }
078
079 @Override
080 public boolean isInterrupted() {
081 return super.isInterrupted() || interruptForced;
082 }
083
084 private Client client;
085 private URI nextRequestURI = null;
086
087 @Override
088 public void run() {
089 Response response = null;
090 while (!isInterrupted()) {
091 try {
092 if (keyManagerChannelManager.unregisterThreadIfMoreThanDesiredThreadCount(this))
093 return;
094
095 if (client == null) {
096 ClientConfig clientConfig = new DefaultClientConfig(JAXBContextResolver.class);
097 client = Client.create(clientConfig);
098 }
099
100 if (nextRequestURI == null) {
101 String s = keyManagerChannelManager.getKeyManagerChannelURL().toString();
102 if (!s.endsWith("/"))
103 s += '/';
104
105 nextRequestURI = new URL(s + "nextRequest/" + keyManagerChannelManager.getSessionManager().getCryptoSessionIDPrefix()).toURI();
106 }
107
108 WebResource.Builder nextRequestWebResourceBuilder = client.resource(
109 nextRequestURI
110 ).type(MediaType.APPLICATION_XML_TYPE).accept(MediaType.APPLICATION_XML_TYPE);
111
112 if (response == null)
113 response = new NullResponse(); // It seems Jersey does not allow null as entity :-(
114
115 Request request;
116 try {
117 request = nextRequestWebResourceBuilder.post(Request.class, response);
118 } catch (UniformInterfaceException x) {
119 // Unfortunately, the Jersey client does not simply return null, but throws this exception,
120 // if the service returns null. Hence we catch it and check for code 204 (NO_CONTENT), which is no
121 // error, but expected. Alternatively, we could introduce a NullRequest (similar to the NullResponse),
122 // but I think, checking for 204 is cleaner than sending dummy objects around. Marco :-)
123 if (x.getResponse().getStatus() == ClientResponse.Status.NO_CONTENT.getStatusCode())
124 request = null;
125 else
126 throw x;
127 }
128
129 response = null; // we processed the last response (if any) and thus have to clear this now to prevent sending it again.
130
131 if (request != null) {
132 try {
133 RequestHandler<Request> requestHandler = keyManagerChannelManager.getRequestHandler(request);
134 response = requestHandler.handle(request);
135 } catch (Throwable t) {
136 logger.error("run: " + t, t);
137 response = new ErrorResponse(request, t);
138 }
139
140 if (response == null)
141 response = new NullResponse(request);
142 }
143 } catch (UniformInterfaceException x) {
144 try {
145 InputStream in = x.getResponse().getEntityInputStream();
146 BufferedReader r = new BufferedReader(new InputStreamReader(in, "UTF-8"));
147 StringWriter sw = new StringWriter();
148 String s;
149 while (null != (s = r.readLine()))
150 sw.append(s);
151
152 in.close();
153 logger.error("run: " + x + "\n" + sw, x);
154 } catch (Exception y) {
155 logger.error("run: Caught exception while processing UniformInterfaceException: " + y, y);
156 }
157 try { Thread.sleep(5000); } catch (InterruptedException e) { doNothing(); } // prevent hammering on the server
158 } catch (Exception x) {
159 logger.error("run: " + x, x);
160 try { Thread.sleep(5000); } catch (InterruptedException e) { doNothing(); } // prevent hammering on the server
161 }
162 }
163 }
164
165 private static final void doNothing() { }
166 }