Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.usergrid.rest.applications; import java.io.InputStream; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.HeaderParam; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.PathSegment; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import org.apache.usergrid.rest.RootResource; import org.apache.usergrid.services.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import org.apache.usergrid.persistence.Entity; import org.apache.usergrid.persistence.EntityManager; import org.apache.usergrid.persistence.Query; import org.apache.usergrid.rest.AbstractContextResource; import org.apache.usergrid.rest.ApiResponse; import org.apache.usergrid.rest.applications.assets.AssetsResource; import org.apache.usergrid.rest.security.annotations.RequireApplicationAccess; import org.apache.usergrid.security.oauth.AccessInfo; import org.apache.usergrid.services.assets.data.AssetUtils; import org.apache.usergrid.services.assets.data.BinaryStore; import org.apache.usergrid.utils.InflectionUtils; import org.apache.commons.lang.StringUtils; import com.sun.jersey.api.json.JSONWithPadding; import com.sun.jersey.core.provider.EntityHolder; import com.sun.jersey.multipart.BodyPart; import com.sun.jersey.multipart.BodyPartEntity; import com.sun.jersey.multipart.FormDataBodyPart; import com.sun.jersey.multipart.FormDataMultiPart; import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE; import static org.apache.commons.lang.StringUtils.isNotBlank; import static org.apache.usergrid.services.ServiceParameter.addParameter; import static org.apache.usergrid.services.ServicePayload.batchPayload; import static org.apache.usergrid.services.ServicePayload.idListPayload; import static org.apache.usergrid.services.ServicePayload.payload; import static org.apache.usergrid.utils.JsonUtils.mapToJsonString; import static org.apache.usergrid.utils.JsonUtils.normalizeJsonTree; @Component @Scope("prototype") @Produces({ MediaType.APPLICATION_JSON, "application/javascript", "application/x-javascript", "text/ecmascript", "application/ecmascript", "text/jscript" }) public class ServiceResource extends AbstractContextResource { private static final Logger LOG = LoggerFactory.getLogger(ServiceResource.class); private static final String FILE_FIELD_NAME = "file"; @Autowired private BinaryStore binaryStore; protected ServiceManager services; List<ServiceParameter> serviceParameters = null; public ServiceResource() { } @Override public void setParent(AbstractContextResource parent) { super.setParent(parent); if (parent instanceof ServiceResource) { services = ((ServiceResource) parent).services; } } public ServiceResource getServiceResourceParent() { if (parent instanceof ServiceResource) { return (ServiceResource) parent; } return null; } public ServiceManager getServices() { return services; } public UUID getApplicationId() { return services.getApplicationId(); } public List<ServiceParameter> getServiceParameters() { if (serviceParameters != null) { return serviceParameters; } if (getServiceResourceParent() != null) { return getServiceResourceParent().getServiceParameters(); } serviceParameters = new ArrayList<ServiceParameter>(); return serviceParameters; } public static List<ServiceParameter> addMatrixParams(List<ServiceParameter> parameters, UriInfo ui, PathSegment ps) throws Exception { MultivaluedMap<String, String> params = ps.getMatrixParameters(); if (params != null) { Query query = Query.fromQueryParams(params); if (query != null) { parameters = addParameter(parameters, query); } } return parameters; } public static List<ServiceParameter> addQueryParams(List<ServiceParameter> parameters, UriInfo ui) throws Exception { MultivaluedMap<String, String> params = ui.getQueryParameters(); if (params != null) { //TODO TN query parameters are not being correctly decoded here. The URL encoded strings //aren't getting decoded properly Query query = Query.fromQueryParams(params); if (query != null) { parameters = addParameter(parameters, query); } } return parameters; } @Path("file") public AbstractContextResource getFileResource(@Context UriInfo ui) throws Exception { LOG.debug("in assets in ServiceResource"); addParameter(getServiceParameters(), "assets"); PathSegment ps = getFirstPathSegment("assets"); if (ps != null) { addMatrixParams(getServiceParameters(), ui, ps); } return getSubResource(AssetsResource.class); } @Path(RootResource.ENTITY_ID_PATH) public AbstractContextResource addIdParameter(@Context UriInfo ui, @PathParam("entityId") PathSegment entityId) throws Exception { LOG.debug("ServiceResource.addIdParameter"); UUID itemId = UUID.fromString(entityId.getPath()); addParameter(getServiceParameters(), itemId); addMatrixParams(getServiceParameters(), ui, entityId); return getSubResource(ServiceResource.class); } @Path("{itemName}") public AbstractContextResource addNameParameter(@Context UriInfo ui, @PathParam("itemName") PathSegment itemName) throws Exception { LOG.debug("ServiceResource.addNameParameter"); LOG.debug("Current segment is {}", itemName.getPath()); if (itemName.getPath().startsWith("{")) { Query query = Query.fromJsonString(itemName.getPath()); if (query != null) { addParameter(getServiceParameters(), query); } } else { addParameter(getServiceParameters(), itemName.getPath()); } addMatrixParams(getServiceParameters(), ui, itemName); return getSubResource(ServiceResource.class); } public ServiceResults executeServiceRequest(UriInfo ui, ApiResponse response, ServiceAction action, ServicePayload payload) throws Exception { LOG.debug("ServiceResource.executeServiceRequest"); boolean tree = "true".equalsIgnoreCase(ui.getQueryParameters().getFirst("tree")); boolean collectionGet = false; if (action == ServiceAction.GET) { collectionGet = (getServiceParameters().size() == 1 && InflectionUtils.isPlural(getServiceParameters().get(0))); } addQueryParams(getServiceParameters(), ui); ServiceRequest r = services.newRequest(action, tree, getServiceParameters(), payload); response.setServiceRequest(r); ServiceResults results = r.execute(); if (results != null) { if (results.hasData()) { response.setData(results.getData()); } if (results.getServiceMetadata() != null) { response.setMetadata(results.getServiceMetadata()); } Query query = r.getLastQuery(); if (query != null) { if (query.hasSelectSubjects()) { response.setList(query.getSelectionResults(results)); response.setCount(response.getList().size()); response.setNext(results.getNextResult()); response.setPath(results.getPath()); return results; } } if (collectionGet) { response.setCount(results.size()); } response.setResults(results); } httpServletRequest.setAttribute("applicationId", services.getApplicationId()); return results; } @GET @Produces({ MediaType.APPLICATION_JSON, MediaType.TEXT_HTML }) @RequireApplicationAccess public JSONWithPadding executeGet(@Context UriInfo ui, @QueryParam("callback") @DefaultValue("callback") String callback) throws Exception { LOG.debug("ServiceResource.executeGet"); ApiResponse response = createApiResponse(); response.setAction("get"); response.setApplication(services.getApplication()); response.setParams(ui.getQueryParameters()); executeServiceRequest(ui, response, ServiceAction.GET, null); return new JSONWithPadding(response, callback); } @SuppressWarnings({ "unchecked" }) public ServicePayload getPayload(Object json) { ServicePayload payload = null; json = normalizeJsonTree(json); if (json instanceof Map) { Map<String, Object> jsonMap = (Map<String, Object>) json; payload = payload(jsonMap); } else if (json instanceof List) { List<?> jsonList = (List<?>) json; if (jsonList.size() > 0) { if (jsonList.get(0) instanceof UUID) { payload = idListPayload((List<UUID>) json); } else if (jsonList.get(0) instanceof Map) { payload = batchPayload((List<Map<String, Object>>) jsonList); } } } if (payload == null) { payload = new ServicePayload(); } return payload; } @POST @RequireApplicationAccess @Consumes(MediaType.APPLICATION_JSON) public JSONWithPadding executePost(@Context UriInfo ui, EntityHolder<Object> body, @QueryParam("callback") @DefaultValue("callback") String callback) throws Exception { LOG.debug("ServiceResource.executePost"); Object json = body.getEntity(); ApiResponse response = createApiResponse(); response.setAction("post"); response.setApplication(services.getApplication()); response.setParams(ui.getQueryParameters()); ServicePayload payload = getPayload(json); executeServiceRequest(ui, response, ServiceAction.POST, payload); return new JSONWithPadding(response, callback); } @PUT @RequireApplicationAccess @Consumes(MediaType.APPLICATION_JSON) public JSONWithPadding executePut(@Context UriInfo ui, Map<String, Object> json, @QueryParam("callback") @DefaultValue("callback") String callback) throws Exception { LOG.debug("ServiceResource.executePut"); ApiResponse response = createApiResponse(); response.setAction("put"); services.getApplicationRef(); response.setApplication(services.getApplication()); response.setParams(ui.getQueryParameters()); ServicePayload payload = getPayload(json); executeServiceRequest(ui, response, ServiceAction.PUT, payload); return new JSONWithPadding(response, callback); } @DELETE @RequireApplicationAccess public JSONWithPadding executeDelete(@Context UriInfo ui, @QueryParam("callback") @DefaultValue("callback") String callback) throws Exception { LOG.debug("ServiceResource.executeDelete"); ApiResponse response = createApiResponse(); response.setAction("delete"); response.setApplication(services.getApplication()); response.setParams(ui.getQueryParameters()); ServiceResults sr = executeServiceRequest(ui, response, ServiceAction.DELETE, null); for (Entity entity : sr.getEntities()) { if (entity.getProperty(AssetUtils.FILE_METADATA) != null) { binaryStore.delete(services.getApplicationId(), entity); } } return new JSONWithPadding(response, callback); } // TODO Temporarily removed until we test further // @Produces("text/csv") // @GET // @RequireApplicationAccess // @Consumes("text/csv") // public String executeGetCsv(@Context UriInfo ui, // @QueryParam("callback") @DefaultValue("callback") String callback) // throws Exception { // ui.getQueryParameters().putSingle("pad", "true"); // JSONWithPadding jsonp = executeGet(ui, callback); // // StringBuilder builder = new StringBuilder(); // if ((jsonp != null) && (jsonp.getJsonSource() instanceof ApiResponse)) { // ApiResponse apiResponse = (ApiResponse) jsonp.getJsonSource(); // if ((apiResponse.getCounters() != null) // && (apiResponse.getCounters().size() > 0)) { // List<AggregateCounterSet> counters = apiResponse.getCounters(); // int size = counters.get(0).getValues().size(); // List<AggregateCounter> firstCounterList = counters.get(0) // .getValues(); // if (size > 0) { // builder.append("timestamp"); // for (AggregateCounterSet counterSet : counters) { // builder.append(","); // builder.append(counterSet.getName()); // } // builder.append("\n"); // SimpleDateFormat formatter = new SimpleDateFormat( // "yyyy-MM-dd HH:mm:ss.SSS"); // for (int i = 0; i < size; i++) { // // yyyy-mm-dd hh:mm:ss.000 // builder.append(formatter.format(new Date( // firstCounterList.get(i).getTimestamp()))); // for (AggregateCounterSet counterSet : counters) { // List<AggregateCounter> counterList = counterSet // .getValues(); // builder.append(","); // builder.append(counterList.get(i).getValue()); // } // builder.append("\n"); // } // } // } else if ((apiResponse.getEntities() != null) // && (apiResponse.getEntities().size() > 0)) { // for (Entity entity : apiResponse.getEntities()) { // builder.append(entity.getUuid()); // builder.append(","); // builder.append(entity.getType()); // builder.append(","); // builder.append(mapToJsonString(entity)); // } // // } // } // return builder.toString(); // } public static String wrapWithCallback(AccessInfo accessInfo, String callback) { return wrapWithCallback(mapToJsonString(accessInfo), callback); } public static String wrapWithCallback(String json, String callback) { if (StringUtils.isNotBlank(callback)) { json = callback + "(" + json + ")"; } return json; } public static MediaType jsonMediaType(String callback) { return isNotBlank(callback) ? new MediaType("application", "javascript") : APPLICATION_JSON_TYPE; } /** ************** the following is file attachment (Asset) support ********************* */ @POST @RequireApplicationAccess @Consumes(MediaType.MULTIPART_FORM_DATA) public JSONWithPadding executeMultiPartPost(@Context UriInfo ui, @QueryParam("callback") @DefaultValue("callback") String callback, FormDataMultiPart multiPart) throws Exception { LOG.debug("ServiceResource.executeMultiPartPost"); return executeMultiPart(ui, callback, multiPart, ServiceAction.POST); } @PUT @RequireApplicationAccess @Consumes(MediaType.MULTIPART_FORM_DATA) public JSONWithPadding executeMultiPartPut(@Context UriInfo ui, @QueryParam("callback") @DefaultValue("callback") String callback, FormDataMultiPart multiPart) throws Exception { LOG.debug("ServiceResource.executeMultiPartPut"); return executeMultiPart(ui, callback, multiPart, ServiceAction.PUT); } private JSONWithPadding executeMultiPart(UriInfo ui, String callback, FormDataMultiPart multiPart, ServiceAction serviceAction) throws Exception { // collect form data values List<BodyPart> bodyParts = multiPart.getBodyParts(); HashMap<String, Object> data = new HashMap<String, Object>(); for (BodyPart bp : bodyParts) { FormDataBodyPart bodyPart = (FormDataBodyPart) bp; if (bodyPart.getMediaType().equals(MediaType.TEXT_PLAIN_TYPE)) { data.put(bodyPart.getName(), bodyPart.getValue()); } else { LOG.info("skipping bodyPart {} of media type {}", bodyPart.getName(), bodyPart.getMediaType()); } } FormDataBodyPart fileBodyPart = multiPart.getField(FILE_FIELD_NAME); if (data.isEmpty() && fileBodyPart != null) { // ensure entity is created even if there are no properties data.put(AssetUtils.FILE_METADATA, new HashMap()); } // process entity ApiResponse response = createApiResponse(); response.setAction(serviceAction.name().toLowerCase()); response.setApplication(services.getApplication()); response.setParams(ui.getQueryParameters()); ServicePayload payload = getPayload(data); ServiceResults serviceResults = executeServiceRequest(ui, response, serviceAction, payload); // process file part if (fileBodyPart != null) { InputStream fileInput = ((BodyPartEntity) fileBodyPart.getEntity()).getInputStream(); if (fileInput != null) { Entity entity = serviceResults.getEntity(); EntityManager em = emf.getEntityManager(getApplicationId()); binaryStore.write(getApplicationId(), entity, fileInput); em.update(entity); serviceResults.setEntity(entity); } } return new JSONWithPadding(response, callback); } @PUT @RequireApplicationAccess @Consumes(MediaType.APPLICATION_OCTET_STREAM) public Response uploadDataStreamPut(@Context UriInfo ui, InputStream uploadedInputStream) throws Exception { return uploadDataStream(ui, uploadedInputStream); } @POST @RequireApplicationAccess @Consumes(MediaType.APPLICATION_OCTET_STREAM) public Response uploadDataStream(@Context UriInfo ui, InputStream uploadedInputStream) throws Exception { ApiResponse response = createApiResponse(); response.setAction("get"); response.setApplication(services.getApplication()); response.setParams(ui.getQueryParameters()); ServiceResults serviceResults = executeServiceRequest(ui, response, ServiceAction.GET, null); Entity entity = serviceResults.getEntity(); binaryStore.write(getApplicationId(), entity, uploadedInputStream); EntityManager em = emf.getEntityManager(getApplicationId()); em.update(entity); return Response.status(200).build(); } @GET @RequireApplicationAccess @Produces(MediaType.WILDCARD) public Response executeStreamGet(@Context UriInfo ui, @PathParam("entityId") PathSegment entityId, @HeaderParam("range") String rangeHeader, @HeaderParam("if-modified-since") String modifiedSince) throws Exception { LOG.debug("ServiceResource.executeStreamGet"); ApiResponse response = createApiResponse(); response.setAction("get"); response.setApplication(services.getApplication()); response.setParams(ui.getQueryParameters()); ServiceResults serviceResults = executeServiceRequest(ui, response, ServiceAction.GET, null); Entity entity = serviceResults.getEntity(); LOG.info("In AssetsResource.findAsset with id: {}, range: {}, modifiedSince: {}", new Object[] { entityId, rangeHeader, modifiedSince }); Map<String, Object> fileMetadata = AssetUtils.getFileMetadata(entity); // return a 302 if not modified Date modified = AssetUtils.fromIfModifiedSince(modifiedSince); if (modified != null) { Long lastModified = (Long) fileMetadata.get(AssetUtils.LAST_MODIFIED); if (lastModified - modified.getTime() < 0) { return Response.status(Response.Status.NOT_MODIFIED).build(); } } boolean range = StringUtils.isNotBlank(rangeHeader); long start = 0, end = 0, contentLength = 0; InputStream inputStream; if (range) { // honor range request, calculate start & end String rangeValue = rangeHeader.trim().substring("bytes=".length()); contentLength = (Long) fileMetadata.get(AssetUtils.CONTENT_LENGTH); end = contentLength - 1; if (rangeValue.startsWith("-")) { start = contentLength - 1 - Long.parseLong(rangeValue.substring("-".length())); } else { String[] startEnd = rangeValue.split("-"); long parsedStart = Long.parseLong(startEnd[0]); if (parsedStart > start && parsedStart < end) { start = parsedStart; } if (startEnd.length > 1) { long parsedEnd = Long.parseLong(startEnd[1]); if (parsedEnd > start && parsedEnd < end) { end = parsedEnd; } } } inputStream = binaryStore.read(getApplicationId(), entity, start, end - start); } else { // no range inputStream = binaryStore.read(getApplicationId(), entity); } // return 404 if not found if (inputStream == null) { return Response.status(Response.Status.NOT_FOUND).build(); } Long lastModified = (Long) fileMetadata.get(AssetUtils.LAST_MODIFIED); Response.ResponseBuilder responseBuilder = Response.ok(inputStream) .type((String) fileMetadata.get(AssetUtils.CONTENT_TYPE)).lastModified(new Date(lastModified)); if (fileMetadata.get(AssetUtils.E_TAG) != null) { responseBuilder.tag((String) fileMetadata.get(AssetUtils.E_TAG)); } if (range) { responseBuilder.header("Content-Range", "bytes " + start + "-" + end + "/" + contentLength); } return responseBuilder.build(); } }