1 /*
2 * Copyright (C) 2007 ETH Zurich
3 *
4 * This file is part of Fosstrak (www.fosstrak.org).
5 *
6 * Fosstrak is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License version 2.1, as published by the Free Software Foundation.
9 *
10 * Fosstrak is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with Fosstrak; if not, write to the Free
17 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301 USA
19 */
20
21 package org.fosstrak.epcis.utils;
22
23 import java.io.BufferedReader;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.InputStreamReader;
27 import java.io.PrintWriter;
28 import java.net.ServerSocket;
29 import java.net.Socket;
30 import java.net.SocketException;
31 import java.nio.ByteBuffer;
32 import java.nio.CharBuffer;
33 import java.nio.charset.Charset;
34 import java.nio.charset.CharsetDecoder;
35
36 /**
37 * This class implements a simple web server listening for responses from the
38 * EPCIS Query Callback interface. The server is not multi-threaded, so it will
39 * only accept one request at a time. It will only allow one instance
40 * (singleton) and will be bound to a predefined port on localhost.
41 *
42 * @author Marco Steybe
43 */
44 public final class QueryCallbackListener extends Thread {
45
46 private static final int PORT = 8899;
47
48 private static QueryCallbackListener instance = null;
49
50 private ServerSocket server = null;
51
52 private boolean isRunning = false;
53
54 private String response = null;
55
56 /**
57 * Instantiates a new SubscriptionResponseListener listening on the given
58 * port.
59 *
60 * @throws IOException
61 * If an error setting up the communication socket occurred.
62 */
63 private QueryCallbackListener() throws IOException {
64 System.out.println("listening for query callbacks on port " + PORT + " ...");
65 server = new ServerSocket(PORT);
66 }
67
68 /**
69 * @return The only instance of this class (singleton).
70 * @throws IOException
71 * If an error setting up the communication socket occurred.
72 */
73 public static QueryCallbackListener getInstance() throws IOException {
74 if (instance == null) {
75 instance = new QueryCallbackListener();
76 }
77 return instance;
78 }
79
80 /**
81 * Keeps this listener running until {@link #stopRunning()} is called.
82 *
83 * @see java.lang.Thread#run()
84 */
85 public void run() {
86 isRunning = true;
87 while (isRunning) {
88 Socket client = null;
89 try {
90 client = server.accept();
91 handleConnection(client);
92 } catch (SocketException e) {
93 // server socket closed (stopRunning was called)
94 } catch (IOException e) {
95 e.printStackTrace();
96 } finally {
97 if (client != null) {
98 try {
99 client.close();
100 } catch (IOException e) {
101 e.printStackTrace();
102 }
103 }
104 }
105 }
106 }
107
108 /**
109 * Handles an incoming HTTP connection, reading the contents, and parsing it
110 * as XML.
111 *
112 * @param client
113 * The client Socket.
114 * @throws IOException
115 * If an I/O error occurred.
116 */
117 private void handleConnection(final Socket client) throws IOException {
118 PrintWriter out = new PrintWriter(client.getOutputStream(), true);
119 InputStream is = client.getInputStream();
120 BufferedReader in = new BufferedReader(new InputStreamReader(is));
121
122 // read content length
123 String prefix = "content-length: ";
124 String inputLine = in.readLine().toLowerCase();
125 while (!inputLine.startsWith(prefix)) {
126 // continue reading ...
127 inputLine = in.readLine().toLowerCase();
128 }
129
130 // parse content length
131 String length = inputLine.substring(prefix.length());
132 int len = Integer.parseInt(length);
133
134 inputLine = in.readLine();
135 while (!inputLine.equals("")) {
136 // continue reading ...
137 inputLine = in.readLine();
138 }
139
140 // read, decode, and parse xml content (UTF-8 encoded!)
141 byte[] xml = new byte[len];
142 is.read(xml);
143 ByteBuffer buf = ByteBuffer.wrap(xml);
144 Charset charset = Charset.forName("UTF-8");
145 CharsetDecoder decoder = charset.newDecoder();
146 CharBuffer charBuffer = decoder.decode(buf);
147 parseResponse(charBuffer.toString().trim());
148
149 // write response
150 out.write("HTTP/1.0 200 OK\n\n");
151 out.flush();
152
153 // notify everyone waiting on us
154 synchronized (this) {
155 this.notifyAll();
156 }
157
158 out.close();
159 in.close();
160 }
161
162 /**
163 * Extracts the XML contents from the given String.
164 *
165 * @param resp
166 * The response from which the XML contents should be extracted.
167 */
168 private void parseResponse(final String resp) {
169 if (resp.startsWith("<?xml")) {
170 // remove xml declaration
171 int index = resp.indexOf("?>") + 2;
172 if (index >= 0) {
173 response = resp.substring(index).trim();
174 }
175 }
176 }
177
178 /**
179 * @return The received XML response.
180 */
181 public String fetchResponse() {
182 String resp = this.response;
183 this.response = null; // reset
184 return resp;
185 }
186
187 /**
188 * @return Wheter this thread is running.
189 */
190 public boolean isRunning() {
191 return isRunning;
192 }
193
194 /**
195 * Stops this thread from running.
196 */
197 public void stopRunning() {
198 isRunning = false;
199 instance = null;
200 try {
201 server.close();
202 } catch (IOException e) {
203 e.printStackTrace();
204 }
205 }
206
207 }