Better bytes-to-String method for verbose output
[zxing.git] / zxingorg / src / com / google / zxing / web / DecodeServlet.java
1 /*
2  * Copyright 2008 ZXing authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.google.zxing.web;
18
19 import com.google.zxing.DecodeHintType;
20 import com.google.zxing.MultiFormatReader;
21 import com.google.zxing.Reader;
22 import com.google.zxing.ReaderException;
23 import com.google.zxing.Result;
24 import com.google.zxing.client.j2se.BufferedImageMonochromeBitmapSource;
25 import com.google.zxing.client.result.ParsedResult;
26 import com.google.zxing.client.result.ResultParser;
27 import org.apache.commons.fileupload.FileItem;
28 import org.apache.commons.fileupload.FileUploadException;
29 import org.apache.commons.fileupload.disk.DiskFileItemFactory;
30 import org.apache.commons.fileupload.servlet.ServletFileUpload;
31 import org.apache.http.Header;
32 import org.apache.http.HttpException;
33 import org.apache.http.HttpMessage;
34 import org.apache.http.HttpResponse;
35 import org.apache.http.HttpVersion;
36 import org.apache.http.client.HttpClient;
37 import org.apache.http.client.methods.HttpGet;
38 import org.apache.http.conn.PlainSocketFactory;
39 import org.apache.http.conn.Scheme;
40 import org.apache.http.conn.SchemeRegistry;
41 import org.apache.http.conn.ssl.SSLSocketFactory;
42 import org.apache.http.impl.client.DefaultHttpClient;
43 import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
44 import org.apache.http.params.BasicHttpParams;
45 import org.apache.http.params.HttpParams;
46 import org.apache.http.params.HttpProtocolParams;
47
48 import javax.imageio.ImageIO;
49 import javax.mail.Authenticator;
50 import javax.servlet.ServletConfig;
51 import javax.servlet.ServletException;
52 import javax.servlet.http.HttpServlet;
53 import javax.servlet.http.HttpServletRequest;
54 import javax.servlet.http.HttpServletResponse;
55 import java.awt.image.BufferedImage;
56 import java.io.IOException;
57 import java.io.InputStream;
58 import java.io.OutputStreamWriter;
59 import java.io.Writer;
60 import java.net.URI;
61 import java.net.URISyntaxException;
62 import java.net.UnknownHostException;
63 import java.util.Hashtable;
64 import java.util.List;
65 import java.util.Timer;
66 import java.util.logging.Logger;
67
68 /**
69  * {@link HttpServlet} which decodes images containing barcodes. Given a URL, it will
70  * retrieve the image and decode it. It can also process image files uploaded via POST.
71  * 
72  * @author Sean Owen (srowen@google.com)
73  */
74 public final class DecodeServlet extends HttpServlet {
75
76   private static final long MAX_IMAGE_SIZE = 500000L;
77   private static final long EMAIL_CHECK_INTERVAL = 2L * 60 * 1000;
78
79   private static final Logger log = Logger.getLogger(DecodeServlet.class.getName());
80
81   static final Hashtable<DecodeHintType, Object> HINTS;
82
83   static {
84     HINTS = new Hashtable<DecodeHintType, Object>(3);
85     HINTS.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
86   }
87
88   private HttpClient client;
89   private DiskFileItemFactory diskFileItemFactory;
90   private Timer emailTimer;
91
92   @Override
93   public void init(ServletConfig servletConfig) throws ServletException {
94
95     Logger logger = Logger.getLogger("com.google.zxing");
96     logger.addHandler(new ServletContextLogHandler(servletConfig.getServletContext()));
97
98     String emailAddress = servletConfig.getInitParameter("emailAddress");
99     String emailPassword = servletConfig.getInitParameter("emailPassword");
100     if (emailAddress == null || emailPassword == null) {
101       throw new ServletException("emailAddress or emailPassword not specified");
102     }
103
104     HttpParams params = new BasicHttpParams();
105     HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
106
107     SchemeRegistry registry = new SchemeRegistry();
108     registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
109     registry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
110
111     client = new DefaultHttpClient(new ThreadSafeClientConnManager(params, registry), params);
112
113     diskFileItemFactory = new DiskFileItemFactory();
114
115     Authenticator emailAuthenticator = new EmailAuthenticator(emailAddress, emailPassword);
116     emailTimer = new Timer("Email decoder timer", true);
117     emailTimer.schedule(new DecodeEmailTask(emailAddress, emailAuthenticator), 0L, EMAIL_CHECK_INTERVAL);
118
119     log.info("DecodeServlet configured");
120   }
121
122   @Override
123   protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
124
125     String imageURIString = request.getParameter("u");
126     if (imageURIString == null || imageURIString.length() == 0) {
127       response.sendRedirect("badurl.jspx");
128       return;
129     }
130
131     if (!(imageURIString.startsWith("http://") || imageURIString.startsWith("https://"))) {
132       imageURIString = "http://" + imageURIString;
133     }
134
135     URI imageURI;
136     try {
137       imageURI = new URI(imageURIString);
138     } catch (URISyntaxException urise) {
139       response.sendRedirect("badurl.jspx");
140       return;
141     }
142
143     HttpGet getRequest = new HttpGet(imageURI);
144
145     try {
146       HttpResponse getResponse = client.execute(getRequest);
147       if (getResponse.getStatusLine().getStatusCode() != HttpServletResponse.SC_OK) {
148         response.sendRedirect("badurl.jspx");
149         return;
150       }
151       if (!isSizeOK(getResponse)) {
152         response.sendRedirect("badimage.jspx");
153         return;
154       }
155       log.info("Decoding " + imageURI);
156       InputStream is = getResponse.getEntity().getContent();
157       try {
158         processStream(is, request, response);
159       } finally {
160         is.close();
161       }
162     } catch (InterruptedException ie) {
163       getRequest.abort();
164       response.sendRedirect("badurl.jspx");
165     } catch (HttpException he) {
166       getRequest.abort();
167       response.sendRedirect("badurl.jspx");
168     } catch (UnknownHostException uhe) {
169       getRequest.abort();
170       response.sendRedirect("badurl.jspx");
171     }
172
173   }
174
175   @Override
176   protected void doPost(HttpServletRequest request, HttpServletResponse response)
177           throws ServletException, IOException {
178
179     if (!ServletFileUpload.isMultipartContent(request)) {
180       response.sendRedirect("badimage.jspx");
181       return;
182     }
183
184     ServletFileUpload upload = new ServletFileUpload(diskFileItemFactory);
185     upload.setFileSizeMax(MAX_IMAGE_SIZE);
186
187     // Parse the request
188     try {
189       for (FileItem item : (List<FileItem>) upload.parseRequest(request)) {
190         if (!item.isFormField()) {
191           if (item.getSize() <= MAX_IMAGE_SIZE) {
192             log.info("Decoding uploaded file");
193             InputStream is = item.getInputStream();
194             try {
195               processStream(is, request, response);
196             } finally {
197               is.close();
198             }
199           } else {
200             response.sendRedirect("badimage.jspx");
201           }
202           break;
203         }
204       }
205     } catch (FileUploadException fue) {
206       response.sendRedirect("badimage.jspx");
207     }
208
209   }
210
211   private static void processStream(InputStream is, HttpServletRequest request, HttpServletResponse response)
212       throws ServletException, IOException {
213     BufferedImage image = ImageIO.read(is);
214     if (image == null) {
215       response.sendRedirect("badimage.jspx");
216       return;
217     }
218
219     Reader reader = new MultiFormatReader();
220     Result result;
221     try {
222       result = reader.decode(new BufferedImageMonochromeBitmapSource(image), HINTS);
223     } catch (ReaderException re) {
224       log.info("DECODE FAILED: " + re.toString());
225       response.sendRedirect("notfound.jspx");
226       return;
227     }
228
229     if (request.getParameter("full") == null) {
230       response.setContentType("text/plain");
231       response.setCharacterEncoding("UTF8");
232       Writer out = new OutputStreamWriter(response.getOutputStream(), "UTF8");
233       try {
234         out.write(result.getText());
235       } finally {
236         out.close();
237       }
238     } else {
239       request.setAttribute("result", result);
240       byte[] rawBytes = result.getRawBytes();
241       if (rawBytes != null) {
242         request.setAttribute("rawBytesString", arrayToString(rawBytes));
243       } else {
244         request.setAttribute("rawBytesString", "(Not applicable)");
245       }
246       ParsedResult parsedResult = ResultParser.parseResult(result);
247       request.setAttribute("parsedResult", parsedResult);
248       request.getRequestDispatcher("decoderesult.jspx").forward(request, response);
249     }
250   }
251
252   private static boolean isSizeOK(HttpMessage getResponse) {
253     Header lengthHeader = getResponse.getLastHeader("Content-Length");
254     if (lengthHeader != null) {
255       long length = Long.parseLong(lengthHeader.getValue());
256       if (length > MAX_IMAGE_SIZE) {
257         return false;
258       }
259     }
260     return true;
261   }
262
263   private static String arrayToString(byte[] bytes) {
264     int length = bytes.length;
265     StringBuilder result = new StringBuilder(length << 2);
266     int i = 0;
267     while (i < length) {
268       int max = Math.min(i + 8, length);
269       for (int j = i; j < max; j++) {
270         int value = bytes[j] & 0xFF;
271         result.append(Integer.toHexString(value / 16));
272         result.append(Integer.toHexString(value % 16));
273         result.append(' ');
274       }
275       result.append('\n');
276       i += 8;
277     }
278     for (int j = i - 8; j < length; j++) {
279       result.append(Integer.toHexString(bytes[j] & 0xFF));
280       result.append(' ');
281     }
282     return result.toString();
283   }
284
285   @Override
286   public void destroy() {
287     log.config("DecodeServlet shutting down...");
288     emailTimer.cancel();
289   }
290
291 }