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    }