Created
August 24, 2012 05:02
-
-
Save angeloh/3445595 to your computer and use it in GitHub Desktop.
part of routes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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" | |
} | |
] | |
} | |
] | |
} | |
] | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | |
*/ | |
} | |
}); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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" | |
} | |
] | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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