public Resolution save() throws DaoException { Link newLink = null; // Attach link to logged in user. link.user = getLoggedInUser(); try { newLink = (Link) getDaoManager() .transaction( new DaoCommand() { @Override public Object execute() throws DaoException { Link newLink = getDaoManager() .getLinkDao() .create(link.build(getDaoManager().getLinkThumbsUtil())); if (newLink != null && thumb != null) { // Create thumbnails. Unfortunately a connection will // remain open when this happens, but we do need to roll // back if something bad happens. // TODO: this could be done inside the LinkDao; however, // we would need to pass the BufferedImage to it (in the // Link.Builder?). linkThumbsUtil.create(newLink, thumb); } return newLink; } }); } catch (Exception e) { if (newLink != null && thumb != null) { // Something went wrong, clean up after ourselves. linkThumbsUtil.delete(newLink); } ValidationErrors errors = new ValidationErrors(); errors.addGlobalError(new ScopedLocalizableError("validation", "daoException")); // NOTE: There are a couple of options how to do this, one: // return new ForwardResolution(this.getClass(), "details"); // or two: getContext().setValidationErrors(errors); return getContext().getSourcePageResolution(); } return new RedirectResolution(ViewLinkActionBean.class) .addParameter("link", newLink.getId()) .addParameter("title", newLink.getUrlSafeTitle()); }
@ValidationMethod(on = "save", priority = 4) // Most expensive .: done last and if no errors. public void validateThumbUrl(ValidationErrors errors) { if (link.sourceThumbUrl == null) { // This is optional, so validation should occur only if there is a // value; however, we must set __some__ value for when this Link is // created. link.sourceThumbUrl = ""; return; } ByteArrayOutputStream outputStream = null; HttpURLConnection conn = null; InputStream inputStream = null; try { URL url = new URL(link.sourceThumbUrl); // TODO: can this follow cyclic redirects forever? HttpURLConnection.setFollowRedirects(true); conn = (HttpURLConnection) url.openConnection(); conn.setReadTimeout(1500); conn.setConnectTimeout(1500); conn.setRequestMethod("GET"); if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) { errors.add(THUMB_URL_FIELD, new ScopedLocalizableError("validation", "urlNotFound")); return; } if (!conn.getContentType().startsWith("image/")) { errors.add(THUMB_URL_FIELD, new ScopedLocalizableError("validation", "imageInvalidType")); return; } if (conn.getContentLength() > THUMB_MAX_BYTES) { errors.add(THUMB_URL_FIELD, new ScopedLocalizableError("validation", "imageTooLarge")); return; } inputStream = conn.getInputStream(); outputStream = new ByteArrayOutputStream(); int totalBytesRead = 0; byte[] buffer = new byte[URL_READ_BUFFER_SIZE]; while (totalBytesRead < THUMB_MAX_BYTES) { int bytesToRead = Math.min(buffer.length, THUMB_MAX_BYTES - totalBytesRead); int bytesRead = inputStream.read(buffer, 0, bytesToRead); if (bytesRead > 0) { outputStream.write(buffer, 0, bytesRead); totalBytesRead += bytesRead; } else if (bytesRead == -1) { break; } } if (inputStream.read() != -1) { errors.add(THUMB_URL_FIELD, new ScopedLocalizableError("validation", "imageTooLarge")); return; } } catch (MalformedURLException e) { logger.debug("Malformed URL: " + e); errors.add(THUMB_URL_FIELD, new ScopedLocalizableError("validation", "urlMalformed")); return; } catch (IOException e) { logger.debug("URL not found: " + e); errors.add(THUMB_URL_FIELD, new ScopedLocalizableError("validation", "urlNotFound")); return; } finally { // Make sure we clean up any InputStream-s and/or connections. if (inputStream != null) { try { inputStream.close(); } catch (IOException ex) { } } if (conn != null) { conn.disconnect(); } } try { thumb = ImageIO.read(new ByteArrayInputStream(outputStream.toByteArray())); } catch (IOException e) { logger.debug("Thumbnail cannot be interpretted as image: " + e); errors.add(THUMB_URL_FIELD, new ScopedLocalizableError("validation", "imageInvalid")); return; } }