1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 }