Refactored the MonochromeBitmapSource class hierarchy into LuminanceSource, Binarizer...
[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.BarcodeFormat;
20 import com.google.zxing.BinaryBitmap;
21 import com.google.zxing.DecodeHintType;
22 import com.google.zxing.LuminanceSource;
23 import com.google.zxing.MultiFormatReader;
24 import com.google.zxing.Reader;
25 import com.google.zxing.ReaderException;
26 import com.google.zxing.Result;
27 import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
28 import com.google.zxing.client.result.ParsedResult;
29 import com.google.zxing.client.result.ResultParser;
30 import com.google.zxing.common.GlobalHistogramBinarizer;
31
32 import org.apache.commons.fileupload.FileItem;
33 import org.apache.commons.fileupload.FileUploadException;
34 import org.apache.commons.fileupload.disk.DiskFileItemFactory;
35 import org.apache.commons.fileupload.servlet.ServletFileUpload;
36 import org.apache.commons.lang.StringEscapeUtils;
37 import org.apache.http.Header;
38 import org.apache.http.HttpException;
39 import org.apache.http.HttpMessage;
40 import org.apache.http.HttpResponse;
41 import org.apache.http.HttpVersion;
42 import org.apache.http.client.HttpClient;
43 import org.apache.http.client.methods.HttpGet;
44 import org.apache.http.client.params.HttpClientParams;
45 import org.apache.http.conn.scheme.PlainSocketFactory;
46 import org.apache.http.conn.scheme.Scheme;
47 import org.apache.http.conn.scheme.SchemeRegistry;
48 import org.apache.http.conn.ssl.SSLSocketFactory;
49 import org.apache.http.impl.client.DefaultHttpClient;
50 import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
51 import org.apache.http.params.BasicHttpParams;
52 import org.apache.http.params.HttpParams;
53 import org.apache.http.params.HttpProtocolParams;
54
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.SocketException;
61 import java.net.URI;
62 import java.net.URISyntaxException;
63 import java.net.UnknownHostException;
64 import java.util.Hashtable;
65 import java.util.List;
66 import java.util.Vector;
67 import java.util.logging.Logger;
68
69 import javax.imageio.ImageIO;
70 import javax.servlet.ServletConfig;
71 import javax.servlet.ServletException;
72 import javax.servlet.http.HttpServlet;
73 import javax.servlet.http.HttpServletRequest;
74 import javax.servlet.http.HttpServletResponse;
75
76 /**
77  * {@link HttpServlet} which decodes images containing barcodes. Given a URL, it will
78  * retrieve the image and decode it. It can also process image files uploaded via POST.
79  * 
80  * @author Sean Owen
81  */
82 public final class DecodeServlet extends HttpServlet {
83
84   private static final long MAX_IMAGE_SIZE = 500000L;
85
86   private static final Logger log = Logger.getLogger(DecodeServlet.class.getName());
87
88   static final Hashtable<DecodeHintType, Object> HINTS;
89
90   static {
91     HINTS = new Hashtable<DecodeHintType, Object>(5);
92     HINTS.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
93     Vector<BarcodeFormat> possibleFormats = new Vector<BarcodeFormat>();
94     possibleFormats.add(BarcodeFormat.UPC_A);
95     possibleFormats.add(BarcodeFormat.UPC_E);
96     possibleFormats.add(BarcodeFormat.EAN_8);
97     possibleFormats.add(BarcodeFormat.EAN_13);
98     possibleFormats.add(BarcodeFormat.CODE_39);
99     possibleFormats.add(BarcodeFormat.CODE_128);
100     possibleFormats.add(BarcodeFormat.ITF);
101     possibleFormats.add(BarcodeFormat.QR_CODE);
102     possibleFormats.add(BarcodeFormat.DATAMATRIX);
103     HINTS.put(DecodeHintType.POSSIBLE_FORMATS, possibleFormats);
104   }
105
106   private HttpClient client;
107   private DiskFileItemFactory diskFileItemFactory;
108
109   @Override
110   public void init(ServletConfig servletConfig) {
111
112     Logger logger = Logger.getLogger("com.google.zxing");
113     logger.addHandler(new ServletContextLogHandler(servletConfig.getServletContext()));
114
115     HttpParams params = new BasicHttpParams();
116     HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
117
118     SchemeRegistry registry = new SchemeRegistry();
119     registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
120     registry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
121
122     client = new DefaultHttpClient(new ThreadSafeClientConnManager(params, registry), params);
123
124     diskFileItemFactory = new DiskFileItemFactory();
125
126     log.info("DecodeServlet configured");
127   }
128
129   @Override
130   protected void doGet(HttpServletRequest request, HttpServletResponse response)
131       throws ServletException, IOException {
132     String imageURIString = request.getParameter("u");
133     if (imageURIString == null || imageURIString.length() == 0) {
134       response.sendRedirect("badurl.jspx");
135       return;
136     }
137
138     if (!(imageURIString.startsWith("http://") || imageURIString.startsWith("https://"))) {
139       imageURIString = "http://" + imageURIString;
140     }
141
142     URI imageURI;
143     try {
144       imageURI = new URI(imageURIString);
145     } catch (URISyntaxException urise) {
146       response.sendRedirect("badurl.jspx");
147       return;
148     }
149
150     HttpGet getRequest = new HttpGet(imageURI);
151     getRequest.addHeader("Connection", "close"); // Avoids CLOSE_WAIT socket issue?
152
153     try {
154       HttpResponse getResponse = client.execute(getRequest);
155       if (getResponse.getStatusLine().getStatusCode() != HttpServletResponse.SC_OK) {
156         response.sendRedirect("badurl.jspx");
157         return;
158       }
159       if (!isSizeOK(getResponse)) {
160         response.sendRedirect("badimage.jspx");
161         return;
162       }
163       log.info("Decoding " + imageURI);
164       InputStream is = getResponse.getEntity().getContent();
165       try {
166         processStream(is, request, response);
167       } finally {
168         is.close();
169       }
170     } catch (IllegalArgumentException iae) {
171       // Thrown if hostname is bad or null
172       getRequest.abort();
173       response.sendRedirect("badurl.jspx");
174     } catch (SocketException se) {
175       // Thrown if hostname is bad or null
176       getRequest.abort();
177       response.sendRedirect("badurl.jspx");
178     } catch (UnknownHostException uhe) {
179       getRequest.abort();
180       response.sendRedirect("badurl.jspx");
181     }
182
183   }
184
185   @Override
186   protected void doPost(HttpServletRequest request, HttpServletResponse response)
187           throws ServletException, IOException {
188
189     if (!ServletFileUpload.isMultipartContent(request)) {
190       response.sendRedirect("badimage.jspx");
191       return;
192     }
193
194     ServletFileUpload upload = new ServletFileUpload(diskFileItemFactory);
195     upload.setFileSizeMax(MAX_IMAGE_SIZE);
196
197     // Parse the request
198     try {
199       for (FileItem item : (List<FileItem>) upload.parseRequest(request)) {
200         if (!item.isFormField()) {
201           if (item.getSize() <= MAX_IMAGE_SIZE) {
202             log.info("Decoding uploaded file");
203             InputStream is = item.getInputStream();
204             try {
205               processStream(is, request, response);
206             } finally {
207               is.close();
208             }
209           } else {
210             response.sendRedirect("badimage.jspx");
211           }
212           break;
213         }
214       }
215     } catch (FileUploadException fue) {
216       response.sendRedirect("badimage.jspx");
217     }
218
219   }
220
221   private static void processStream(InputStream is, HttpServletRequest request,
222       HttpServletResponse response) throws ServletException, IOException {
223     BufferedImage image = ImageIO.read(is);
224     if (image == null) {
225       response.sendRedirect("badimage.jspx");
226       return;
227     }
228
229     Reader reader = new MultiFormatReader();
230     Result result;
231     try {
232       LuminanceSource source = new BufferedImageLuminanceSource(image);
233       BinaryBitmap bitmap = new BinaryBitmap(new GlobalHistogramBinarizer(source));
234       result = reader.decode(bitmap, HINTS);
235     } catch (ReaderException re) {
236       log.info("DECODE FAILED: " + re.toString());
237       response.sendRedirect("notfound.jspx");
238       return;
239     }
240
241     if (request.getParameter("full") == null) {
242       response.setContentType("text/plain");
243       response.setCharacterEncoding("UTF8");
244       Writer out = new OutputStreamWriter(response.getOutputStream(), "UTF8");
245       try {
246         out.write(result.getText());
247       } finally {
248         out.close();
249       }
250     } else {
251       request.setAttribute("result", result);
252       byte[] rawBytes = result.getRawBytes();
253       if (rawBytes != null) {
254         request.setAttribute("rawBytesString", arrayToString(rawBytes));
255       } else {
256         request.setAttribute("rawBytesString", "(Not applicable)");
257       }
258       String text = result.getText();
259       if (text != null) {
260         request.setAttribute("text", StringEscapeUtils.escapeXml(text));
261       } else {
262         request.setAttribute("text", "(Not applicable)");
263       }
264       ParsedResult parsedResult = ResultParser.parseResult(result);
265       request.setAttribute("parsedResult", parsedResult);
266       String displayResult = parsedResult.getDisplayResult();
267       if (displayResult != null) {
268         request.setAttribute("displayResult", StringEscapeUtils.escapeXml(displayResult));
269       } else {
270         request.setAttribute("displayResult", "(Not applicable)");
271       }
272       request.getRequestDispatcher("decoderesult.jspx").forward(request, response);
273     }
274   }
275
276   private static boolean isSizeOK(HttpMessage getResponse) {
277     Header lengthHeader = getResponse.getLastHeader("Content-Length");
278     if (lengthHeader != null) {
279       long length = Long.parseLong(lengthHeader.getValue());
280       if (length > MAX_IMAGE_SIZE) {
281         return false;
282       }
283     }
284     return true;
285   }
286
287   private static String arrayToString(byte[] bytes) {
288     int length = bytes.length;
289     StringBuilder result = new StringBuilder(length << 2);
290     int i = 0;
291     while (i < length) {
292       int max = Math.min(i + 8, length);
293       for (int j = i; j < max; j++) {
294         int value = bytes[j] & 0xFF;
295         result.append(Integer.toHexString(value / 16));
296         result.append(Integer.toHexString(value % 16));
297         result.append(' ');
298       }
299       result.append('\n');
300       i += 8;
301     }
302     for (int j = i - 8; j < length; j++) {
303       result.append(Integer.toHexString(bytes[j] & 0xFF));
304       result.append(' ');
305     }
306     return result.toString();
307   }
308
309   @Override
310   public void destroy() {
311     log.config("DecodeServlet shutting down...");
312   }
313
314 }