/* * 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 com.amazonaws.AmazonServiceException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.jaxrs.json.annotation.JSONP; import com.google.cloud.storage.StorageException; import org.apache.commons.lang.StringUtils; import org.apache.usergrid.management.OrganizationConfig; import org.apache.usergrid.management.OrganizationConfigProps; import org.apache.usergrid.persistence.Entity; import org.apache.usergrid.persistence.EntityManager; import org.apache.usergrid.persistence.Query; import org.apache.usergrid.persistence.QueryUtils; import org.apache.usergrid.rest.AbstractContextResource; import org.apache.usergrid.rest.ApiResponse; import org.apache.usergrid.rest.RootResource; import org.apache.usergrid.rest.applications.assets.AssetsResource; import org.apache.usergrid.rest.security.annotations.CheckPermissionsForPath; import org.apache.usergrid.security.oauth.AccessInfo; import org.apache.usergrid.services.*; import org.apache.usergrid.services.assets.BinaryStoreFactory; import org.apache.usergrid.services.assets.data.*; import org.apache.usergrid.services.exceptions.AwsPropertiesNotFoundException; import org.apache.usergrid.utils.JsonUtils; import org.glassfish.jersey.media.multipart.BodyPart; import org.glassfish.jersey.media.multipart.BodyPartEntity; import org.glassfish.jersey.media.multipart.FormDataBodyPart; import org.glassfish.jersey.media.multipart.FormDataMultiPart; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeanInfoFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import javax.ws.rs.*; import javax.ws.rs.core.*; import java.io.InputStream; import java.util.*; import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE; import static org.apache.usergrid.management.AccountCreationProps.PROPERTIES_USERGRID_BINARY_UPLOADER; @Component @Scope("prototype") @Produces({ MediaType.APPLICATION_JSON, "application/javascript", "application/x-javascript", "text/ecmascript", "application/ecmascript", "text/jscript" }) public class ServiceResource extends AbstractContextResource { protected static final Logger logger = LoggerFactory.getLogger( ServiceResource.class ); private static final String FILE_FIELD_NAME = "file"; private BinaryStore binaryStore; @Autowired private BinaryStoreFactory binaryStoreFactory; 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 String getOrganizationName() { return services.getApplication().getOrganizationName(); } public List<ServiceParameter> getServiceParameters() { if ( serviceParameters != null ) { return serviceParameters; } if ( getServiceResourceParent() != null ) { return getServiceResourceParent().getServiceParameters(); } serviceParameters = new ArrayList<>(); 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 && params.size() > 0) { Query query = Query.fromQueryParams( params ); if ( query != null ) { parameters = ServiceParameter.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 && params.size() > 0) { //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.size() > 0 && parameters.get( 0 ).isId()){ query = Query.fromUUID( parameters.get( 0 ).getId() ); } if ( query != null ) { parameters = ServiceParameter.addParameter( parameters, query ); } } return parameters; } @Path("file") public AbstractContextResource getFileResource( @Context UriInfo ui ) throws Exception { if(logger.isTraceEnabled()){ logger.trace( "ServiceResource.getFileResource" ); } ServiceParameter.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 { if(logger.isTraceEnabled()){ logger.trace( "ServiceResource.addIdParameter" ); } UUID itemId = UUID.fromString( entityId.getPath() ); ServiceParameter.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 { if(logger.isTraceEnabled()){ logger.trace( "ServiceResource.addNameParameter" ); logger.trace( "Current segment is {}", itemName.getPath() ); } if ( itemName.getPath().startsWith( "{" ) ) { Query query = Query.fromJsonString( itemName.getPath() ); if ( query != null ) { ServiceParameter.addParameter( getServiceParameters(), query ); } } else { ServiceParameter.addParameter( getServiceParameters(), itemName.getPath() ); } addMatrixParams( getServiceParameters(), ui, itemName ); return getSubResource( ServiceResource.class ); } public ServiceResults executeServiceGetRequestForSettings(UriInfo ui, ApiResponse response, ServiceAction action, ServicePayload payload) throws Exception { if(logger.isTraceEnabled()){ logger.trace( "ServiceResource.executeServiceRequest" ); } boolean tree = "true".equalsIgnoreCase( ui.getQueryParameters().getFirst( "tree" ) ); String connectionQueryParm = ui.getQueryParameters().getFirst("connections"); boolean returnInboundConnections = true; boolean returnOutboundConnections = true; addQueryParams( getServiceParameters(), ui ); ServiceRequest r = services.newRequest( action, tree, getServiceParameters(), payload, returnInboundConnections, returnOutboundConnections, false); response.setServiceRequest( r ); AbstractCollectionService abstractCollectionService = new AbstractCollectionService(); // abstractCollectionService ServiceResults results = abstractCollectionService.getCollectionSettings( 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( QueryUtils.getSelectionResults( query, results ) ); response.setCount( response.getList().size() ); response.setNext( results.getNextResult() ); response.setPath( results.getPath() ); return results; } } response.setResults( results ); } httpServletRequest.setAttribute( "applicationId", services.getApplicationId() ); return results; } public ServiceResults executeServicePostRequestForSettings(UriInfo ui, ApiResponse response, ServiceAction action, ServicePayload payload) throws Exception { if(logger.isTraceEnabled()){ logger.trace( "ServiceResource.executeServiceRequest" ); } boolean tree = "true".equalsIgnoreCase( ui.getQueryParameters().getFirst( "tree" ) ); String connectionQueryParm = ui.getQueryParameters().getFirst("connections"); boolean returnInboundConnections = true; boolean returnOutboundConnections = true; addQueryParams( getServiceParameters(), ui ); ServiceRequest r = services.newRequest( action, tree, getServiceParameters(), payload, returnInboundConnections, returnOutboundConnections, false); response.setServiceRequest( r ); AbstractCollectionService abstractCollectionService = new AbstractCollectionService(); ServiceResults results = abstractCollectionService.postCollectionSettings( 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( QueryUtils.getSelectionResults( query, results ) ); response.setCount( response.getList().size() ); response.setNext( results.getNextResult() ); response.setPath( results.getPath() ); return results; } } response.setResults( results ); } httpServletRequest.setAttribute( "applicationId", services.getApplicationId() ); return results; } public ServiceResults executeServiceRequest( UriInfo ui, ApiResponse response, ServiceAction action, ServicePayload payload ) throws Exception { if(logger.isTraceEnabled()){ logger.trace( "ServiceResource.executeServiceRequest" ); } boolean tree = "true".equalsIgnoreCase( ui.getQueryParameters().getFirst( "tree" ) ); String connectionQueryParm = ui.getQueryParameters().getFirst("connections"); boolean returnInboundConnections = true; boolean returnOutboundConnections = true; // connection info can be blocked only for GETs if (action == ServiceAction.GET) { if ("none".equalsIgnoreCase(connectionQueryParm)) { returnInboundConnections = false; returnOutboundConnections = false; } else if ("in".equalsIgnoreCase(connectionQueryParm)) { returnInboundConnections = true; returnOutboundConnections = false; } else if ("out".equalsIgnoreCase(connectionQueryParm)) { returnInboundConnections = false; returnOutboundConnections = true; } else if ("all".equalsIgnoreCase(connectionQueryParm)) { returnInboundConnections = true; returnOutboundConnections = true; } else { if (connectionQueryParm != null) { // unrecognized parameter logger.error(String.format( "Invalid connections query parameter=%s, ignoring.", connectionQueryParm)); } // use the default query parameter functionality OrganizationConfig orgConfig = management.getOrganizationConfigForApplication(services.getApplicationId()); String defaultConnectionQueryParm = orgConfig.getProperty(OrganizationConfigProps.ORGPROPERTIES_DEFAULT_CONNECTION_PARAM); returnInboundConnections = (defaultConnectionQueryParm.equals("in")) || (defaultConnectionQueryParm.equals("all")); returnOutboundConnections = (defaultConnectionQueryParm.equals("out")) || (defaultConnectionQueryParm.equals("all")); } } boolean analyzeQueryOnly = Boolean.valueOf(ui.getQueryParameters().getFirst("analyzeOnly")); boolean collectionGet = false; if ( action == ServiceAction.GET ) { collectionGet = getServiceParameters().size() == 1; } addQueryParams( getServiceParameters(), ui ); ServiceRequest r = services.newRequest( action, tree, getServiceParameters(), payload, returnInboundConnections, returnOutboundConnections, analyzeQueryOnly); 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( QueryUtils.getSelectionResults( query, 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; } @CheckPermissionsForPath @GET @Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_HTML, "application/javascript"}) @JSONP public ApiResponse executeGet( @Context UriInfo ui, @QueryParam("callback") @DefaultValue("callback") String callback ) throws Exception { if(logger.isTraceEnabled()){ logger.trace( "ServiceResource.executeGet" ); } ApiResponse response = createApiResponse(); response.setAction( "get" ); response.setApplication( services.getApplication() ); response.setParams( ui.getQueryParameters() ); executeServiceRequest( ui, response, ServiceAction.GET, null ); return response; } @SuppressWarnings({ "unchecked" }) public ServicePayload getPayload( Object json ) { ServicePayload payload = null; json = JsonUtils.normalizeJsonTree( json ); if ( json instanceof Map ) { Map<String, Object> jsonMap = ( Map<String, Object> ) json; payload = ServicePayload.payload( jsonMap ); } else if ( json instanceof List ) { List<?> jsonList = ( List<?> ) json; if ( jsonList.size() > 0 ) { if ( jsonList.get( 0 ) instanceof UUID ) { payload = ServicePayload.idListPayload( ( List<UUID> ) json ); } else if ( jsonList.get( 0 ) instanceof Map ) { payload = ServicePayload.batchPayload( ( List<Map<String, Object>> ) jsonList ); } } } if ( payload == null ) { payload = new ServicePayload(); } return payload; } /** * Necessary to work around inexplicable problems with EntityHolder. * See above. */ public ApiResponse executePostWithObject( @Context UriInfo ui, Object json, @QueryParam("callback") @DefaultValue("callback") String callback ) throws Exception { if(logger.isTraceEnabled()){ logger.trace( "ServiceResource.executePostWithMap" ); } 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 response; } /** * Necessary to work around inexplicable problems with EntityHolder. * See above. */ public ApiResponse executePutWithMap( @Context UriInfo ui, Map<String, Object> json, @QueryParam("callback") @DefaultValue("callback") String callback ) throws Exception { ApiResponse response = createApiResponse(); response.setAction( "put" ); response.setApplication( services.getApplication() ); response.setParams( ui.getQueryParameters() ); ServicePayload payload = getPayload( json ); executeServiceRequest( ui, response, ServiceAction.PUT, payload ); return response; } @CheckPermissionsForPath @POST @Consumes(MediaType.APPLICATION_JSON) @JSONP @Produces({MediaType.APPLICATION_JSON, "application/javascript"}) public ApiResponse executePost( @Context UriInfo ui, String body, @QueryParam("callback") @DefaultValue("callback") String callback ) throws Exception { if(logger.isTraceEnabled()){ logger.trace( "ServiceResource.executePost: body = {}", body ); } Object json; if ( StringUtils.isEmpty( body ) ) { json = null; } else { json = readJsonToObject( body ); } 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 response; } @CheckPermissionsForPath @PUT @Consumes(MediaType.APPLICATION_JSON) @JSONP @Produces({MediaType.APPLICATION_JSON, "application/javascript"}) public ApiResponse executePut( @Context UriInfo ui, String body, @QueryParam("callback") @DefaultValue("callback") String callback ) throws Exception { if(logger.isTraceEnabled()){ logger.trace( "ServiceResource.executePut" ); } ObjectMapper mapper = new ObjectMapper(); Map<String, Object> json = mapper.readValue( body, mapTypeReference ); return executePutWithMap(ui, json, callback); } @CheckPermissionsForPath @DELETE @JSONP @Produces({MediaType.APPLICATION_JSON, "application/javascript"}) public ApiResponse executeDelete( @Context UriInfo ui, @QueryParam("callback") @DefaultValue("callback") String callback, @QueryParam("app_delete_confirm") String confirmAppDelete ) throws Exception { if(logger.isTraceEnabled()){ logger.trace( "ServiceResource.executeDelete" ); } ApiResponse response = createApiResponse(); response.setAction( "delete" ); response.setApplication( services.getApplication() ); response.setParams( ui.getQueryParameters() ); ServiceResults sr = executeServiceRequest( ui, response, ServiceAction.DELETE, null ); // if we deleted an entity (and not a connection or collection) then // we may need to clean up binary asset data associated with that entity if ( !sr.getResultsType().equals( ServiceResults.Type.CONNECTION ) && !sr.getResultsType().equals( ServiceResults.Type.COLLECTION )) { for ( Entity entity : sr.getEntities() ) { if ( entity.getProperty( AssetUtils.FILE_METADATA ) != null ) { try { binaryStore.delete( services.getApplicationId(), entity ); }catch(AwsPropertiesNotFoundException apnfe){ logger.error( "Amazon Property needed for this operation not found",apnfe ); response.setError( "500","Amazon Property needed for this operation not found",apnfe ); } } } } return response; } // 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( JsonUtils.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 StringUtils.isNotBlank( callback ) ? new MediaType( "application", "javascript" ) : APPLICATION_JSON_TYPE; } /** ************** the following is file attachment (Asset) support ********************* */ @CheckPermissionsForPath @POST @Consumes(MediaType.MULTIPART_FORM_DATA) @JSONP @Produces({MediaType.APPLICATION_JSON, "application/javascript"}) public ApiResponse executeMultiPartPost( @Context UriInfo ui, @QueryParam("callback") @DefaultValue("callback") String callback, FormDataMultiPart multiPart ) throws Exception { if(logger.isTraceEnabled()){ logger.trace( "ServiceResource.executeMultiPartPost" ); } return executeMultiPart( ui, callback, multiPart, ServiceAction.POST ); } @CheckPermissionsForPath @PUT @Consumes(MediaType.MULTIPART_FORM_DATA) @JSONP @Produces({MediaType.APPLICATION_JSON, "application/javascript"}) public ApiResponse executeMultiPartPut( @Context UriInfo ui, @QueryParam("callback") @DefaultValue("callback") String callback, FormDataMultiPart multiPart ) throws Exception { if(logger.isTraceEnabled()){ logger.trace( "ServiceResource.executeMultiPartPut" ); } return executeMultiPart( ui, callback, multiPart, ServiceAction.PUT ); } @JSONP @Produces({MediaType.APPLICATION_JSON, "application/javascript"}) private ApiResponse executeMultiPart( UriInfo ui, String callback, FormDataMultiPart multiPart, ServiceAction serviceAction ) throws Exception { // needed for testing this.binaryStore = binaryStoreFactory.getBinaryStore( properties.getProperty(PROPERTIES_USERGRID_BINARY_UPLOADER) ); // collect form data values List<BodyPart> bodyParts = multiPart.getBodyParts(); HashMap<String, Object> data = new HashMap<>(); for ( BodyPart bp : bodyParts ) { FormDataBodyPart bodyPart = ( FormDataBodyPart ) bp; if ( bodyPart.getMediaType().equals( MediaType.TEXT_PLAIN_TYPE ) ) { data.put( bodyPart.getName(), bodyPart.getValue() ); } else { if (logger.isTraceEnabled()) { logger.trace("skipping bodyPart {} of media type {}", bodyPart.getName(), bodyPart.getMediaType()); } } } FormDataBodyPart fileBodyPart = multiPart.getField( FILE_FIELD_NAME ); 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() ); //Updates entity with fields that are in text/plain as per loop above if(data.get( FILE_FIELD_NAME )==null){ data.put( FILE_FIELD_NAME,null ); } 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() ); try { binaryStore.write( getApplicationId(), entity, fileInput ); } catch ( AwsPropertiesNotFoundException apnfe){ logger.error( "Amazon Property needed for this operation not found",apnfe ); response.setError( "500","Amazon Property needed for this operation not found",apnfe ); } catch ( RuntimeException re){ logger.error(re.getMessage()); response.setError( "500", re ); } //em.update( entity ); entity = serviceResults.getEntity(); serviceResults.setEntity( entity ); } } return response; } @CheckPermissionsForPath @PUT @Consumes(MediaType.APPLICATION_OCTET_STREAM) public Response uploadDataStreamPut( @Context UriInfo ui, InputStream uploadedInputStream ) throws Exception { return uploadDataStream( ui, uploadedInputStream ); } @CheckPermissionsForPath @POST @Consumes(MediaType.APPLICATION_OCTET_STREAM) public Response uploadDataStream( @Context UriInfo ui, InputStream uploadedInputStream ) throws Exception { //needed for testing this.binaryStore = binaryStoreFactory.getBinaryStore( properties.getProperty(PROPERTIES_USERGRID_BINARY_UPLOADER) ); 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(); try { binaryStore.write( getApplicationId(), entity, uploadedInputStream ); }catch(AwsPropertiesNotFoundException apnfe){ logger.error( "Amazon Property needed for this operation not found",apnfe ); return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); }catch ( RuntimeException re ){ logger.error(re.getMessage()); return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); } EntityManager em = emf.getEntityManager( getApplicationId() ); em.update( entity ); return Response.status( 200 ).build(); } @CheckPermissionsForPath @GET @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 { if(logger.isTraceEnabled()){ logger.trace( "ServiceResource.executeStreamGet" ); } // needed for testing this.binaryStore = binaryStoreFactory.getBinaryStore( properties.getProperty(PROPERTIES_USERGRID_BINARY_UPLOADER) ); 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(); if(logger.isTraceEnabled()){ logger.trace( "In ServiceResource.executeStreamGet with id: {}, range: {}, modifiedSince: {}", 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; } } } try { inputStream = binaryStore.read( getApplicationId(), entity, start, end - start ); }catch(AwsPropertiesNotFoundException apnfe){ logger.error( "Amazon Property needed for this operation not found",apnfe ); return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); }catch(RuntimeException re){ logger.error(re.getMessage()); return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); } } else { // no range try { inputStream = binaryStore.read( getApplicationId(), entity ); }catch(AwsPropertiesNotFoundException apnfe){ logger.error( "Amazon Property needed for this operation not found",apnfe ); return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); } catch(AmazonServiceException ase){ if( ase.getStatusCode() > 499 ){ logger.error(ase.getMessage()); }else if(logger.isDebugEnabled()){ logger.debug(ase.getMessage()); } return Response.status(ase.getStatusCode()).build(); } catch (StorageException se){ if( se.getCode() > 499 ){ logger.error(se.getMessage()); }else if(logger.isDebugEnabled()){ logger.debug(se.getMessage()); } return Response.status(se.getCode()).build(); } catch(RuntimeException re){ logger.error(re.getMessage()); return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); } } // 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(); } }