Skip to content

Instantly share code, notes, and snippets.

@angeloh
Created August 24, 2012 05:02
Show Gist options
  • Save angeloh/3445595 to your computer and use it in GitHub Desktop.
Save angeloh/3445595 to your computer and use it in GitHub Desktop.
part of routes
{
"apiVersion": "beta",
"swaggerVersion": "1.1",
"basePath": "http://localhost:9000",
"resourcePath": "/image",
"apis": [
{
"path": "/image",
"description": "Operations about image post",
"operations": [
{
"httpMethod": "POST",
"summary": "Upload image to the store",
"responseClass": "void",
"nickname": "imagePost",
"parameters": [
{
"name": "origImageId",
"description": "GUID for original image file",
"paramType": "body",
"required": false,
"allowMultiple": false,
"dataType": "String"
},
{
"name": "imageBlob",
"description": "image blob",
"paramType": "body",
"required": true,
"allowMultiple": false,
"dataType": "File"
}
],
"errorResponses": [
{
"code": 400,
"reason": "Expecting multipart/form-data request body"
}
]
}
]
}
]
}
package controllers;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Callable;
import javax.imageio.ImageIO;
import javax.inject.Inject;
import javax.ws.rs.POST;
import models.ImageInfo;
import org.codehaus.jackson.node.ObjectNode;
import org.imgscalr.Scalr;
import org.jets3t.service.S3Service;
import org.jets3t.service.S3ServiceException;
import org.jets3t.service.ServiceException;
import org.jets3t.service.acl.AccessControlList;
import org.jets3t.service.acl.GroupGrantee;
import org.jets3t.service.acl.Permission;
import org.jets3t.service.acl.gs.AllUsersGrantee;
import org.jets3t.service.model.S3Bucket;
import org.jets3t.service.model.S3Object;
import com.wordnik.swagger.annotations.Api;
import com.wordnik.swagger.annotations.ApiError;
import com.wordnik.swagger.annotations.ApiErrors;
import com.wordnik.swagger.annotations.ApiOperation;
import com.wordnik.swagger.annotations.ApiParamImplicit;
import com.wordnik.swagger.annotations.ApiParamsImplicit;
import com.wordnik.swagger.annotations.ApiResponse;
import common.Config;
import play.Logger;
import play.Play;
import play.libs.Akka;
import play.libs.F.Promise;
import play.libs.Json;
import play.mvc.Controller;
import play.mvc.Http;
import play.mvc.Http.MultipartFormData.FilePart;
import play.mvc.Http.RequestBody;
import play.mvc.BodyParser;
import play.mvc.Result;
import play.libs.F.Function;
@Api(value = "/image", description = "Operations about image post")
public class ImagePost extends Controller {
@Inject static S3Service s3Service;
/**
* Http body
* String origImageId(optional) : orig image GUID
* String thumbImageId(optional) : thumbnail image GUID
* Binary imageBlob : orig image
*
* @return the result
* @throws NoSuchAlgorithmException
* @throws IOException
* @throws ServiceException
*/
@POST
@ApiOperation(value = "Upload image to the store")
@ApiErrors(value = { @ApiError(code = 400, reason = "Expecting multipart/form-data request body") })
@ApiParamsImplicit({
@ApiParamImplicit(
name = Config.IMAGE_ORIG_ID_PARAM, value = "GUID for original image file",
required = false, dataType = "String", paramType = "body"),
@ApiParamImplicit(
name = Config.IMAGE_BLOB_PARAM, value = "image blob",
required = true, dataType = "File", paramType = "body")
})
@ApiResponse(value = "{\"origImageId\":\"GUID-1\",\"thumbImageId\":\"GUID-1_t\",\"status\":\"OK\"," +
"\"message\":\"\"}")
@BodyParser.Of(BodyParser.MultipartFormData.class)
public static Result imagePost() {
RequestBody body = request().body();
Http.MultipartFormData formData = body.asMultipartFormData();
if (formData == null) {
return badRequest("Expecting multipart/form-data request body");
}
// XTODO if there is no solution, just upload file and send back uuid to client.
// simple form field
String imageId = null;
/*
* Form<AnEntity> filledForm = AnEntity.bindFromRequest(); if (filledForm.hasErrors()) { return
* badRequest(views.html.upload.render(filledForm)); } else { AnEntity resource = filledForm.get();
* MultipartFormData body = request().body().asMultipartFormData(); FilePart resourceFile =
* body.getFile("resourceFile"); }
*/
Map<String, String[]> formUrls = formData.asFormUrlEncoded();
// DataPart imageIdPart = formData.getData(Config.IMAGE_ORIG_ID_PARAM);
if (formUrls.get(Config.IMAGE_ORIG_ID_PARAM) != null) {
imageId = formUrls.get(Config.IMAGE_ORIG_ID_PARAM)[0];
}
// uploaded file
FilePart imageFilePart = formData.getFile(Config.IMAGE_BLOB_PARAM);
if (imageFilePart == null) {
String respStatus = Config.ERROR_STATUS;
String respMessage = "Need " + Config.IMAGE_ORIG_ID_PARAM + " and " + Config.IMAGE_BLOB_PARAM;
ObjectNode result = Json.newObject();
result.put(Config.RESP_STATUS_PARAM, respStatus);
result.put(Config.RESP_MESSAGE_PARAM, respMessage);
return badRequest(result);
}
File imageDataFile = imageFilePart.getFile();
Promise<ObjectNode> objectNodePromise = doImagePost(imageId, imageDataFile, null);
return async(objectNodePromise.map(new Function<ObjectNode, Result>() {
public Result apply(ObjectNode result) {
if (result.get(Config.RESP_STATUS_PARAM).getTextValue().equals(Config.OK_STATUS)) {
return ok(result);
} else {
return badRequest(result);
}
}
}));
}
public static Promise<ObjectNode> doImagePost(String imageId, final File imageDataFile, final String imgSrc) {
String bucketName = (Play.application().isProd()) ? Play.application().configuration().getString("s3.bucket")
: Play.application().configuration().getString("dev.s3.bucket");
final S3Bucket bucket;
final AccessControlList bucketAcl;
final String finalImageId;
try {
bucket = s3Service.getBucket(bucketName);
bucketAcl = s3Service.getBucketAcl(bucket);
bucketAcl.grantPermission(GroupGrantee.ALL_USERS, Permission.PERMISSION_READ);
} catch (ServiceException e) {
play.Logger.warn("ServiceException in doImagePost: " + e);
ObjectNode result = Json.newObject();
result.put(Config.RESP_STATUS_PARAM, Config.ERROR_STATUS);
result.put(Config.RESP_MESSAGE_PARAM, "S3 ServiceException");
return new Promise<ObjectNode>(result);
}
if (imageId == null) {
finalImageId = UUID.randomUUID().toString();
} else {
finalImageId = imageId;
}
// thumbnail id is always = imageId_t
final String finalThumbId = finalImageId + "_t";
// XTODO use async to upload image
Promise<BufferedImage> promise = Akka.future(new Callable<BufferedImage>() {
public BufferedImage call() {
// upload orig image to s3
File imageFile = new File(finalImageId + ".png");
BufferedImage bufImg = null;
try {
if (imageDataFile != null) {
bufImg = ImageIO.read(imageDataFile);
} else if (imgSrc != null) {
Logger.info("Uploading image from " + imgSrc);
URL imageUrl;
try {
imageUrl = new URL(imgSrc);
} catch (java.net.MalformedURLException e) {
Logger.warn("askForShopinionPost exception: " + e);
return null;
}
bufImg = ImageIO.read(imageUrl);
} else {
return null;
}
ImageIO.write(bufImg, "png", imageFile);
S3Object imageObject = new S3Object(imageFile);
imageObject.setAcl(bucketAcl);
s3Service.putObject(bucket, imageObject);
// remove local file
imageFile.delete();
return bufImg;
} catch (IOException ex) {
play.Logger.error("Error,", ex);
} catch (NoSuchAlgorithmException ex) {
play.Logger.error("Error,", ex);
} catch (S3ServiceException ex) {
play.Logger.error("Error,", ex);
}
return null;
}
});
return promise.map(new Function<BufferedImage, ObjectNode>() {
public ObjectNode apply(BufferedImage origImage) {
if (origImage == null) {
ObjectNode result = Json.newObject();
result.put(Config.RESP_STATUS_PARAM, Config.ERROR_STATUS);
result.put(Config.RESP_MESSAGE_PARAM, "Image Extraction Error");
System.out.println("Image Extraction Error");
return result;
}
// resize image to thumb and upload
BufferedImage thumbnail = Scalr.resize(origImage, Scalr.Method.SPEED, Scalr.Mode.FIT_TO_WIDTH,
Config.IMAGE_WEB_THUMB_WIDTH, Config.IMAGE_WEB_THUMB_HEIGHT, Scalr.OP_ANTIALIAS);// resize
// upload resize image to s3
File thumbFile = new File(finalThumbId + ".png");
ImageInfo imageInfo = null;
ObjectNode result = Json.newObject();
String respStatus = Config.ERROR_STATUS;
String respMessage = "";
try {
ImageIO.write(thumbnail, "png", thumbFile);
S3Object thumbObject = new S3Object(thumbFile);
thumbObject.setAcl(bucketAcl);
thumbObject = s3Service.putObject(bucket, thumbObject);
// remove local file
thumbFile.delete();
// create image record
imageInfo = new ImageInfo(finalImageId, finalThumbId);
imageInfo.setWidth(origImage.getWidth());
imageInfo.setHeight(origImage.getHeight());
imageInfo.save();
// json result
if (imageInfo != null) {
result.put(Config.IMAGE_ORIG_ID_PARAM, finalImageId);
result.put(Config.IMAGE_THUMB_ID_PARAM, finalThumbId);
respStatus = Config.OK_STATUS;
}
} catch (IOException ex) {
play.Logger.error("Error,", ex);
respMessage = "Server Error, failed to upload and store image. Please try again!";
} catch (S3ServiceException ex) {
play.Logger.error("Error,", ex);
respMessage = "Server Error, failed to upload and store image. Please try again!";
} catch (NoSuchAlgorithmException ex) {
play.Logger.error("Error,", ex);
respMessage = "Server Error, failed to upload and store image. Please try again!";
}
result.put(Config.RESP_STATUS_PARAM, respStatus);
result.put(Config.RESP_MESSAGE_PARAM, respMessage);
return result;
/*
* if(!respMessage.isEmpty()) { // ERROR, send error message back return badRequest(result); } if
* (imageInfo != null) { // OK return ok(result); } // BAD, it should not get here return
* badRequest("Expecting multipart/form-data request body");
*/
}
});
}
}
package controllers;
import static akka.pattern.Patterns.ask;
import static common.APIResponse.Status.ERROR;
import static common.APIResponse.Status.OK;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Callable;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import javax.ws.rs.PathParam;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.node.ArrayNode;
import org.codehaus.jackson.node.ObjectNode;
import models.ImageInfo;
import models.InterestInfo;
import models.MemberInfo;
import models.Message;
import com.wordnik.swagger.annotations.Api;
import com.wordnik.swagger.annotations.ApiError;
import com.wordnik.swagger.annotations.ApiErrors;
import com.wordnik.swagger.annotations.ApiOperation;
import com.wordnik.swagger.annotations.ApiParam;
import com.wordnik.swagger.annotations.ApiParamImplicit;
import com.wordnik.swagger.annotations.ApiParamsImplicit;
import com.wordnik.swagger.annotations.ApiResponse;
import actors.MessageWorker;
import akka.actor.ActorRef;
import akka.actor.Props;
import akka.actor.UntypedActor;
import akka.actor.UntypedActorFactory;
import akka.routing.RoundRobinRouter;
import akka.actor.Status.Failure;
import common.APIResponse;
import common.Config;
import common.Config.IMAGE_TYPE;
import play.Play;
import play.libs.Akka;
import play.libs.Json;
import play.libs.F.Promise;
import play.mvc.Result;
@Api(value = "/image", description = "Operations about images sync")
public class ImageSync extends BaseApiController {
@GET
@Path("/sync")
@ApiOperation(value = "Sync images by lastsyncdate and subjectid",
notes = "Value 'lastsyncdate' format is number of milliseconds since the standard base time known as \"the epoch\", " +
"sync latest interests /image/sync?lastsyncdate=<timestampsince1970>&subjectid=12345")
@ApiErrors(value = { @ApiError(code = 400, reason = "Invalid last sync date value") })
@ApiResponse(value = "{\"subjectId\":\"subject-1\",\"imageSets\":[{\"imageSet\":[{\"type\":\"ORIGINAL\"," +
"\"source\":\"https://s3.amazonaws.com/shopinionapp/image-id-2.png\",\"width\":200," +
"\"height\":200},{\"type\":\"THUMB\"," +
"\"source\":\"https://s3.amazonaws.com/shopinionapp/thumb-id-2.png\"," +
"\"width\":200,\"height\":200}]}]}")
public static Result syncImagesBySubject(
@ApiParam(value = "Last sync timestamp, return all if ignored", required = false)
@QueryParam(Config.LAST_SYNC_DATE_LOWER_PARAM)
String lastSyncDate,
@ApiParam(value = "Subject Id", required = true)
@QueryParam(Config.SUBJECT_ID_LOWER_PARAM)
final String subjectId) {
if (subjectId == null) {
String respStatus = Config.ERROR_STATUS;
String respMessage = "Need " + Config.SUBJECT_ID_LOWER_PARAM;
ObjectNode result = Json.newObject();
result.put(Config.RESP_STATUS_PARAM, respStatus);
result.put(Config.RESP_MESSAGE_PARAM, respMessage);
return badRequest(result);
}
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date lastSyncDateObj = new Date();
try {
if (lastSyncDate != null) {
long lastSyncInt = Long.parseLong(lastSyncDate);
lastSyncDateObj = new Date(lastSyncInt);
} else {
lastSyncDateObj = (Date) dateFormat.parse("2011-01-01 01:00:00");
}
} catch (ParseException ex) {
play.Logger.warn("error to parse last sync date", ex);
} catch (NumberFormatException ex) {
play.Logger.warn("error to parse last sync date", ex);
}
final Date lastSyncDateParam = lastSyncDateObj;
Promise<ObjectNode> promiseGet = Akka.future(
new Callable<ObjectNode>() {
public ObjectNode call() {
List<ImageInfo> list = ImageInfo.getImageInfosBySubjectId(subjectId, lastSyncDateParam);
ObjectNode result = Json.newObject();
result.put(Config.SUBJECT_ID_PARAM, subjectId);
ArrayNode arrNode = result.putArray(Config.IMAGE_SYNC_LIST_PARAM);
Iterator<ImageInfo> iter = list.iterator();
while (iter.hasNext()) {
ImageInfo img = iter.next();
ObjectNode imgObjNode = Json.newObject();
ArrayNode imgSetNode = imgObjNode.putArray(Config.IMAGE_SYNC_OBJ_PARAM);
String s3Url = (Play.application().isProd()) ?
Play.application().configuration().getString("s3.path") :
Play.application().configuration().getString("dev.s3.path");
if (img.getImageId() != null) {
ObjectNode imgDetailNode = Json.newObject();
imgDetailNode.put(Config.IMAGE_SYNC_TYPE_PARAM, IMAGE_TYPE.ORIGINAL.toString());
imgDetailNode.put(Config.IMAGE_SYNC_SRC_PARAM, s3Url + img.getImageId() + ".png");
imgDetailNode.put(Config.IMAGE_SYNC_WIDTH_PARAM, img.getWidth());
imgDetailNode.put(Config.IMAGE_SYNC_HEIGHT_PARAM, img.getHeight());
imgSetNode.add(imgDetailNode);
}
if (img.getThumbnailId() != null) {
ObjectNode imgDetailNode = Json.newObject();
imgDetailNode.put(Config.IMAGE_SYNC_TYPE_PARAM, IMAGE_TYPE.THUMB.toString());
imgDetailNode.put(Config.IMAGE_SYNC_SRC_PARAM, s3Url + img.getThumbnailId() + ".png");
imgDetailNode.put(Config.IMAGE_SYNC_WIDTH_PARAM,Config.IMAGE_WEB_THUMB_WIDTH);
imgDetailNode.put(Config.IMAGE_SYNC_HEIGHT_PARAM, Config.IMAGE_WEB_THUMB_HEIGHT);
imgSetNode.add(imgDetailNode);
}
arrNode.add(imgObjNode);
}
return result;
}
}
);
// XTODO when test can return status(async), switch this to async(promise)
ObjectNode result = promiseGet.get();
if(result == null) {
// ERROR, send error message back
return badRequest(result);
}
// OK
return ok(result);
}
}
{
"apiVersion": "beta",
"swaggerVersion": "1.1",
"basePath": "http://localhost:9000",
"apis": [
{
"path": "/image.{format}",
"description": "Operations about images sync"
},
{
"path": "/message.{format}",
"description": "Operations about message post"
},
{
"path": "/contacts.{format}",
"description": "Operations about phone contacts sync"
},
{
"path": "/pusherhooks.{format}",
"description": "Operations about pusher web hooks"
},
{
"path": "/pusherpurge.{format}",
"description": "Operations about pusher channel purge"
},
{
"path": "/register.{format}",
"description": "Operations about register"
},
{
"path": "/interest.{format}",
"description": "Operations about interest sync"
},
{
"path": "/pusherauth.{format}",
"description": "Operations about pusher auth"
},
{
"path": "/userstatusget.{format}",
"description": "Operations about verifying fb user status"
}
]
}
# swagger resources
GET /resources.json controllers.ApiHelpController.getResources()
GET /image.json controllers.ApiHelpController.getResource(path = "/image")
POST /image controllers.ImagePost.imagePost()
GET /image/sync controllers.ImageSync.syncImagesBySubject(lastsyncdate: String, subjectid: String)
@angeloh
Copy link
Author

angeloh commented Aug 24, 2012

Lots of errors in the play output.

[error] c.w.s.j.ApiModelParser - Class com.avaje.ebean.Query is not annotated with a @XmlRootElement annotation, using Query
[error] c.w.s.j.ApiModelParser - Class com.avaje.ebean.Query$Type is not annotated with a @XmlRootElement annotation, using Type
[error] c.w.s.j.ApiModelParser - Class com.avaje.ebean.Query$Type is not annotated with a @XmlRootElement annotation, using Type
[error] c.w.s.j.ApiModelParser - Class com.avaje.ebean.ExpressionFactory is not annotated with a @XmlRootElement annotation, using ExpressionFactory
[error] c.w.s.j.ApiModelParser - Class com.avaje.ebean.ExpressionFactory is not annotated with a @XmlRootElement annotation, using ExpressionFactory
[error] c.w.s.j.ApiModelParser - Class com.avaje.ebean.RawSql is not annotated with a @XmlRootElement annotation, using RawSql
[error] c.w.s.j.ApiModelParser - Class com.avaje.ebean.RawSql is not annotated with a @XmlRootElement annotation, using RawSql
[error] c.w.s.j.ApiModelParser - Class com.avaje.ebean.Query$UseIndex is not annotated with a @XmlRootElement annotation, using UseIndex
[error] c.w.s.j.ApiModelParser - Class com.avaje.ebean.Query$UseIndex is not annotated with a @XmlRootElement annotation, using UseIndex
[error] c.w.s.j.ApiModelParser - Class com.avaje.ebean.ExampleExpression is not annotated with a @XmlRootElement annotation, using ExampleExpression

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment