View Javadoc

1   /*
2    * Copyright (c) 2010 Commsen International. All rights reserved.
3    * 
4    * This file is part of JWebThumb library.
5    *	
6    * JWebThumb library is free software: you can redistribute it and/or modify 
7    * it under the terms of the GNU Lesser General Public License as published by
8    * the Free Software Foundation, either version 2 of the License, or
9    * (at your option) any later version.
10   * 
11   * JWebThumb library is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   * GNU Lesser General Public License for more details.
15   * 
16   * You should have received a copy of the GNU Lesser General Public License
17   * along with JWebThumb library.  If not, see <http://www.gnu.org/licenses/lgpl.html>.
18   */
19  
20  package com.commsen.jwebthumb;
21  
22  import java.io.IOException;
23  import java.io.OutputStream;
24  import java.net.HttpURLConnection;
25  import java.net.MalformedURLException;
26  import java.net.URL;
27  import java.util.ArrayList;
28  import java.util.List;
29  import java.util.logging.Level;
30  import java.util.logging.Logger;
31  
32  import org.apache.commons.io.IOUtils;
33  import org.apache.commons.lang.Validate;
34  
35  import com.commsen.jwebthumb.simplexml.SimpleXmlSerializer;
36  
37  /***
38   * This class provides convenient methods for webthumb's "request", "fetch" and "credits" API calls.
39   * It does not support "status" call. Please use "notifications" instead by extending
40   * {@link WebThumbNotificationServlet} and implementing
41   * {@link WebThumbNotificationServlet#processThumb(String, String)} method.
42   * 
43   * For more details about webthumb's API please visit http://webthumb.bluga.net/apidoc
44   * 
45   * @author <a href="mailto:MilenDyankov@gmail.com">Milen Dyankov</a>
46   * @see http://webthumb.bluga.net/apidoc
47   * 
48   */
49  public class WebThumbService {
50  
51  	private static final String CONTENT_TYPE_TEXT_PLAIN = "text/plain";
52  
53  	private static final String CONTENT_TYPE_TEXT_XML = "text/xml";
54  
55  	private static final Logger LOGGER = Logger.getLogger(WebThumbService.class.getName());
56  
57  	private static final String WEB_THUMB_URL = "http://webthumb.bluga.net/api.php";
58  	private String apikey;
59  
60  
61  	public WebThumbService(String apiKey) {
62  		if (apiKey == null) throw new IllegalArgumentException("apiKey is null!");
63  		this.apikey = apiKey;
64  	}
65  
66  
67  	/***
68  	 * Sends single thumbnail request to webthumb site. For more information see webthumb's request
69  	 * API: http://webthumb.bluga.net/apidoc#request
70  	 * 
71  	 * @param webThumbRequest object containing the request parameters
72  	 * @return job
73  	 * @throws WebThumbException if the request fails for whatever reason
74  	 */
75  	public WebThumbJob sendRequest(WebThumbRequest webThumbRequest) throws WebThumbException {
76  		List<WebThumbRequest> webThumbRequests = new ArrayList<WebThumbRequest>(1);
77  		webThumbRequests.add(webThumbRequest);
78  		List<WebThumbJob> results = sendRequest(webThumbRequests);
79  		if (results != null && !results.isEmpty()) {
80  			return results.get(0);
81  		}
82  		return null;
83  	}
84  
85  
86  	/***
87  	 * Sends thumbnail requests to webthumb site. For more information see webthumb's request API:
88  	 * http://webthumb.bluga.net/apidoc#request
89  	 * 
90  	 * @param webThumbRequests list of objects containing the request parameters
91  	 * @return list of jobs
92  	 * @throws WebThumbException if the request fails for whatever reason
93  	 */
94  	public List<WebThumbJob> sendRequest(List<WebThumbRequest> webThumbRequests) throws WebThumbException {
95  		Validate.notNull(webThumbRequests, "webThumbRequests is null!");
96  		Validate.notEmpty(webThumbRequests, "webThumbRequests is empty!");
97  		Validate.noNullElements(webThumbRequests, "webThumbRequests contains null elements!");
98  
99  		if (LOGGER.isLoggable(Level.FINE)) {
100 			LOGGER.fine("Attempting to send webThumbRequests: " + webThumbRequests);
101 		}
102 		WebThumb webThumb = new WebThumb(apikey, webThumbRequests);
103 		HttpURLConnection connection = sendWebThumb(webThumb);
104 
105 		try {
106 			WebThumbResponse webThumbResponse = SimpleXmlSerializer.parseResponse(connection.getInputStream(), WebThumbResponse.class);
107 			if (LOGGER.isLoggable(Level.FINE)) {
108 				LOGGER.fine("Response processed! Returning: " + webThumbResponse.getJobs());
109 			}
110 			if (webThumbResponse.getError() != null) {
111 				throw new WebThumbException("Server side error: " + webThumbResponse.getError().getValue());
112 			}
113 			if (webThumbResponse.getErrors() != null) {
114 				for (WebThumbError webThumbError : webThumbResponse.getErrors()) {
115 					LOGGER.warning("Server side error: " + webThumbError);
116 				}
117 			}
118 			return webThumbResponse.getJobs();
119 		} catch (IOException e) {
120 			throw new WebThumbException("failed to send request", e);
121 		}
122 	}
123 
124 
125 	/***
126 	 * Fetches single file form webthumb site. Depending on what image format was requested
127 	 * (jpg|png|png8) and what file size is set in {@link WebThumbFetchRequest} returned byte array
128 	 * will be the content of the jpg, png, png8 or zip file.
129 	 * 
130 	 * @param webThumbFetchRequest fetch request containing the job and size to be fetched
131 	 * @return the content of the jpg, png, png8 or zip file.
132 	 * @throws WebThumbException if the file can not be fetched for whatever reason
133 	 */
134 	public byte[] fetch(WebThumbFetchRequest webThumbFetchRequest) throws WebThumbException {
135 		HttpURLConnection connection = getFetchConnection(webThumbFetchRequest);
136 		int contentLength = connection.getContentLength();
137 		if (contentLength != -1) {
138 			byte[] data;
139 			try {
140 				data = IOUtils.toByteArray(connection.getInputStream());
141 			} catch (IOException e) {
142 				throw new WebThumbException("failed to read response", e);
143 			}
144 			if (data.length != contentLength) {
145 				throw new WebThumbException("Read " + data.length + " bytes; Expected " + contentLength + " bytes");
146 			}
147 			if (LOGGER.isLoggable(Level.FINE)) {
148 				LOGGER.fine("Response processed! Returning: " + data.length + " bytes of data");
149 			}
150 			return data;
151 		} else {
152 			throw new WebThumbException("Failed to fetch image! Missing content length!");
153 		}
154 	}
155 
156 
157 	/***
158 	 * Fetches single file form webthumb site and writes its content to given output stream.
159 	 * Depending on what image format was requested (jpg|png|png8) and what file size is set in
160 	 * {@link WebThumbFetchRequest} it will be the content of the jpg, png, png8 or zip file.
161 	 * 
162 	 * @since 0.3
163 	 * 
164 	 * @param webThumbFetchRequest fetch request containing the job and size to be fetched
165 	 * @param outputStream output stream to write the result to
166 	 * @throws WebThumbException if the file can not be fetched for whatever reason
167 	 */
168 	public void fetch(WebThumbFetchRequest webThumbFetchRequest, OutputStream outputStream) throws WebThumbException {
169 		HttpURLConnection connection = getFetchConnection(webThumbFetchRequest);
170 		int contentLength = connection.getContentLength();
171 		if (contentLength != -1) {
172 			try {
173 				IOUtils.copy(connection.getInputStream(), outputStream);
174 			} catch (IOException e) {
175 				throw new WebThumbException("failed to read response", e);
176 			}
177 		} else {
178 			throw new WebThumbException("Failed to fetch image! Missing content length!");
179 		}
180 	}
181 
182 
183 	private HttpURLConnection getFetchConnection(WebThumbFetchRequest webThumbFetchRequest) throws WebThumbException {
184 		Validate.notNull(webThumbFetchRequest, "webThumbFetchRequest is null!");
185 		if (LOGGER.isLoggable(Level.FINE)) {
186 			LOGGER.fine("Attempting to send webThumbFetchRequest: " + webThumbFetchRequest);
187 		}
188 		WebThumb webThumb = new WebThumb(apikey, webThumbFetchRequest);
189 
190 		try {
191 			HttpURLConnection connection = (HttpURLConnection) new URL(WEB_THUMB_URL).openConnection();
192 			connection.setInstanceFollowRedirects(false);
193 			connection.setDoOutput(true);
194 			connection.setRequestMethod("POST");
195 
196 			SimpleXmlSerializer.generateRequest(webThumb, connection.getOutputStream());
197 
198 			int responseCode = connection.getResponseCode();
199 			String contentType = getContentType(connection);
200 			if (LOGGER.isLoggable(Level.FINE)) {
201 				LOGGER.fine("webThumbFetchRequest sent. Got response: " + responseCode + " " + connection.getResponseMessage());
202 				LOGGER.fine("Content type: " + contentType);
203 			}
204 
205 			if (responseCode == HttpURLConnection.HTTP_OK) {
206 				if (CONTENT_TYPE_TEXT_PLAIN.equals(contentType)) {
207 					throw new WebThumbException("Server side error: " + IOUtils.toString(connection.getInputStream()));
208 				}
209 				return connection;
210 			} else if (responseCode == HttpURLConnection.HTTP_INTERNAL_ERROR) {
211 				WebThumbResponse webThumbResponse = SimpleXmlSerializer.parseResponse(connection.getErrorStream(), WebThumbResponse.class);
212 				throw new WebThumbException("Server side error: " + webThumbResponse.getError().getValue());
213 			} else {
214 				throw new WebThumbException("Server side error: " + connection.getResponseCode() + " " + connection.getResponseMessage());
215 			}
216 
217 		} catch (MalformedURLException e) {
218 			throw new WebThumbException("failed to send request", e);
219 		} catch (IOException e) {
220 			throw new WebThumbException("failed to send request", e);
221 		}
222 
223 	}
224 
225 
226 	/***
227 	 * Checks the status of given job or list of jobs.
228 	 * 
229 	 * Make sure not to make more then 1 status request per second. More requests then that may get
230 	 * you temporarily blocked at the firewall. Best Practice is to make a status request at most
231 	 * every 10 seconds.
232 	 * 
233 	 * If your need to make a lot of requests, please look into using notifications instead of
234 	 * polling for status. See {@link WebThumbRequest#setNotify(String)} and
235 	 * {@link WebThumbNotificationServlet} for more details!
236 	 * 
237 	 * @since 0.3
238 	 * 
239 	 * @param webThumbStatusRequest fetch request containing the job and size to be fetched
240 	 * @return list of job statuses
241 	 * @throws WebThumbException if the file can not be fetched for whatever reason
242 	 */
243 	public List<WebThumbStatus> getStatus(WebThumbStatusRequest webThumbStatusRequest) throws WebThumbException {
244 		Validate.notNull(webThumbStatusRequest, "webThumbStatusRequest is null!");
245 		if (LOGGER.isLoggable(Level.FINE)) {
246 			LOGGER.fine("Attempting to send webThumbStatusRequest: " + webThumbStatusRequest);
247 		}
248 
249 		WebThumb webThumb = new WebThumb(apikey, webThumbStatusRequest);
250 		HttpURLConnection connection = sendWebThumb(webThumb);
251 
252 		try {
253 			WebThumbResponse webThumbResponse = SimpleXmlSerializer.parseResponse(connection.getInputStream(), WebThumbResponse.class);
254 			WebThumbJobStatus webThumbJobStatus = webThumbResponse.getJobStatus();
255 			if (webThumbJobStatus == null) {
256 				throw new WebThumbException("response missing 'jobStatus' element!");
257 			}
258 			if (webThumbJobStatus.getErrors() != null) {
259 				for (WebThumbError webThumbError : webThumbJobStatus.getErrors()) {
260 					LOGGER.warning("Server side error: " + webThumbError);
261 				}
262 			}
263 			if (LOGGER.isLoggable(Level.FINE)) {
264 				LOGGER.fine("Response processed! Returning: " + webThumbResponse.getJobStatus().getStatuses());
265 			}
266 			return webThumbResponse.getJobStatus().getStatuses();
267 		} catch (IOException e) {
268 			throw new WebThumbException("failed to send request", e);
269 		}
270 
271 	}
272 
273 
274 	/***
275 	 * Lets you see how many credits you've used and how many subscription/reserve credits you have
276 	 * left.
277 	 * 
278 	 * @return
279 	 * @throws WebThumbException
280 	 */
281 	public WebThumbCredits getCredits() throws WebThumbException {
282 		if (LOGGER.isLoggable(Level.FINE)) {
283 			LOGGER.fine("Attempting to send 'credits' request!");
284 		}
285 
286 		WebThumb webThumb = WebThumb.creditsRequest(apikey);
287 		HttpURLConnection connection = sendWebThumb(webThumb);
288 
289 		try {
290 			WebThumbResponse webThumbResponse = SimpleXmlSerializer.parseResponse(connection.getInputStream(), WebThumbResponse.class);
291 			if (LOGGER.isLoggable(Level.FINE)) {
292 				LOGGER.fine("Response processed! Returning: " + webThumbResponse.getCredits());
293 			}
294 			return webThumbResponse.getCredits();
295 		} catch (IOException e) {
296 			throw new WebThumbException("failed to send request", e);
297 		}
298 	}
299 
300 
301 	/***
302 	 * Helper method used to actually send {@link WebThumb} request and check for common errors.
303 	 * 
304 	 * @param webThumb the request to send
305 	 * @return connection to extract the response from
306 	 * @throws WebThumbException if any error occurs
307 	 */
308 	private HttpURLConnection sendWebThumb(WebThumb webThumb) throws WebThumbException {
309 		try {
310 			HttpURLConnection connection = (HttpURLConnection) new URL(WEB_THUMB_URL).openConnection();
311 			connection.setInstanceFollowRedirects(false);
312 			connection.setDoOutput(true);
313 			connection.setRequestMethod("POST");
314 
315 			SimpleXmlSerializer.generateRequest(webThumb, connection.getOutputStream());
316 
317 			int responseCode = connection.getResponseCode();
318 			String contentType = getContentType(connection);
319 			if (LOGGER.isLoggable(Level.FINE)) {
320 				LOGGER.fine("Request sent! Got response: " + responseCode + " " + connection.getResponseMessage());
321 				LOGGER.fine("Response content type: " + contentType);
322 				LOGGER.fine("Response content encoding: " + connection.getContentEncoding());
323 				LOGGER.fine("Response content length: " + connection.getContentLength());
324 			}
325 
326 			if (responseCode == HttpURLConnection.HTTP_OK) {
327 				if (CONTENT_TYPE_TEXT_PLAIN.equals(contentType)) {
328 					throw new WebThumbException("Server side error: " + IOUtils.toString(connection.getInputStream()));
329 				}
330 				if (!CONTENT_TYPE_TEXT_XML.equals(contentType)) {
331 					throw new WebThumbException("Unknown content type in response: " + contentType);
332 				}
333 				return connection;
334 			} else {
335 				throw new WebThumbException("Server side error: " + connection.getResponseCode() + ") " + connection.getResponseMessage());
336 			}
337 		} catch (MalformedURLException e) {
338 			throw new WebThumbException("failed to send request", e);
339 		} catch (IOException e) {
340 			throw new WebThumbException("failed to send request", e);
341 		}
342 	}
343 
344 
345 	/***
346 	 * Helper method to extract the content type from {@link HttpURLConnection} object
347 	 * 
348 	 * @param connection the connection
349 	 * @return the related part of content type header
350 	 */
351 	private String getContentType(HttpURLConnection connection) {
352 		String s = connection.getContentType();
353 		int semicolonIndex = s.indexOf(';');
354 		if (semicolonIndex < 0) {
355 			return s;
356 		} else {
357 			return s.substring(0, semicolonIndex);
358 		}
359 	}
360 
361 }