You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by je...@apache.org on 2016/02/25 21:27:11 UTC
[23/50] [abbrv] incubator-geode git commit: Merge branch 'develop'
into feature/GEODE-17
http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/5c01d5f4/geode-web-api/src/main/java/com/gemstone/gemfire/rest/internal/web/controllers/PdxBasedCrudController.java
----------------------------------------------------------------------
diff --cc geode-web-api/src/main/java/com/gemstone/gemfire/rest/internal/web/controllers/PdxBasedCrudController.java
index 0000000,99e50d6..726d127
mode 000000,100644..100644
--- a/geode-web-api/src/main/java/com/gemstone/gemfire/rest/internal/web/controllers/PdxBasedCrudController.java
+++ b/geode-web-api/src/main/java/com/gemstone/gemfire/rest/internal/web/controllers/PdxBasedCrudController.java
@@@ -1,0 -1,354 +1,444 @@@
+ /*
+ * 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 com.gemstone.gemfire.rest.internal.web.controllers;
+
+ import java.util.ArrayList;
+ import java.util.List;
+ import java.util.Map;
++import java.util.Set;
+
+ import org.apache.logging.log4j.Logger;
+ import org.springframework.http.HttpHeaders;
+ import org.springframework.http.HttpStatus;
+ import org.springframework.http.MediaType;
+ import org.springframework.http.ResponseEntity;
+ import org.springframework.stereotype.Controller;
+ import org.springframework.util.StringUtils;
+ import org.springframework.web.bind.annotation.PathVariable;
+ import org.springframework.web.bind.annotation.RequestBody;
+ import org.springframework.web.bind.annotation.RequestMapping;
+ import org.springframework.web.bind.annotation.RequestMethod;
+ import org.springframework.web.bind.annotation.RequestParam;
+
++import com.gemstone.gemfire.cache.operations.PutOperationContext;
+ import com.gemstone.gemfire.internal.logging.LogService;
++import com.gemstone.gemfire.internal.security.AuthorizeRequest;
+ import com.gemstone.gemfire.internal.util.ArrayUtils;
+ import com.gemstone.gemfire.rest.internal.web.controllers.support.JSONTypes;
+ import com.gemstone.gemfire.rest.internal.web.controllers.support.RegionData;
+ import com.gemstone.gemfire.rest.internal.web.controllers.support.RegionEntryData;
+ import com.gemstone.gemfire.rest.internal.web.exception.ResourceNotFoundException;
++import com.gemstone.gemfire.rest.internal.web.security.AuthorizationProvider;
++import com.gemstone.gemfire.rest.internal.web.security.RestRequestFilter;
++import com.gemstone.gemfire.security.NotAuthorizedException;
+ import com.wordnik.swagger.annotations.Api;
+ import com.wordnik.swagger.annotations.ApiOperation;
+ import com.wordnik.swagger.annotations.ApiResponse;
+ import com.wordnik.swagger.annotations.ApiResponses;
+
+ /**
+ * The PdxBasedCrudController class serving REST Requests related to the REST CRUD operation on region
+ * <p/>
+ * @author Nilkanth Patel, john blum
+ * @see org.springframework.stereotype.Controller
+ * @since 8.0
+ */
+
+ @Controller("pdxCrudController")
+ @Api(value = "region",
+ description = "region CRUD operations")
+ @RequestMapping(PdxBasedCrudController.REST_API_VERSION)
+ @SuppressWarnings("unused")
+ public class PdxBasedCrudController extends CommonCrudController {
+
+ private static final Logger logger = LogService.getLogger();
+
+ protected static final String REST_API_VERSION = "/v1";
+
+ protected static final String DEFAULT_GETALL_RESULT_LIMIT = "50";
+
+ @Override
+ protected String getRestApiVersion() {
+ return REST_API_VERSION;
+ }
+
+ /**
+ * Creating entry into the region
+ * @param region region name where data will be created
+ * @param key gemfire region key
+ * @param json JSON document that is stored against the key
+ * @return JSON document
+ */
+
+ @RequestMapping(method = RequestMethod.POST, value = "/{region}",
+ consumes = MediaType.APPLICATION_JSON_VALUE,
+ produces = { MediaType.APPLICATION_JSON_VALUE })
+ @ApiOperation(
+ value = "create entry",
+ notes = "Create (put-if-absent) data in region",
+ response = void.class
+ )
+ @ApiResponses( {
+ @ApiResponse( code = 201, message = "Created."),
+ @ApiResponse( code = 400, message = "Data specified (JSON doc) in the request body is invalid." ),
+ @ApiResponse( code = 404, message = "Region does not exist." ),
+ @ApiResponse( code = 409, message = "Key already exist in region."),
+ @ApiResponse( code = 500, message = "GemFire throws an error or exception.")
+ } )
+ public ResponseEntity<?> create(@PathVariable("region") String region,
+ @RequestParam(value = "key", required = false) String key,
+ @RequestBody final String json) {
+
+ key = generateKey(key);
+
+ if(logger.isDebugEnabled()){
+ logger.debug("Posting (creating/putIfAbsent) JSON document ({}) to Region ({}) with Key ({})...",
+ json, region, key);
+ }
+ region = decode(region);
- Object existingPdxObj = null;
+
++ final HttpHeaders headers = new HttpHeaders();
++ headers.setLocation(toUri(region, key));
++
++ //Do request (Pre) authorization if security is enabled.
++ if(AuthorizationProvider.isSecurityEnabled()){
++ setAuthTokenHeader(headers);
++ AuthorizationProvider.init();
++ try{
++ //TODO: add isJson type in OperationContext
++ AuthorizationProvider.putAuthorize(region, key, json, true/*isJson*/, null, PutOperationContext.CREATE);
++ }catch(NotAuthorizedException nae) {
++ return new ResponseEntity<String>(headers, HttpStatus.UNAUTHORIZED);
++ }
++ }
++
++ Object existingPdxObj = null;
+ //Check whether the user has supplied single JSON doc or Array of JSON docs
+ final JSONTypes jsonType = validateJsonAndFindType(json);
+ if(JSONTypes.JSON_ARRAY.equals(jsonType)){
+ existingPdxObj = postValue(region, key, convertJsonArrayIntoPdxCollection(json));
+ }else {
+ existingPdxObj = postValue(region, key, convert(json));
+ }
+
- final HttpHeaders headers = new HttpHeaders();
- headers.setLocation(toUri(region, key));
-
+ if (existingPdxObj != null) {
+ final RegionEntryData<Object> data = new RegionEntryData<Object>(region);
+ data.add(existingPdxObj);
+ headers.setContentType(MediaType.APPLICATION_JSON);
+ return new ResponseEntity<RegionEntryData<?>>(data, headers, HttpStatus.CONFLICT);
+ } else {
+ return new ResponseEntity<String>(headers, HttpStatus.CREATED);
+ }
+ }
+
+ /**
+ * Read all or fixed number of data in a given Region
+ * @param region gemfire region name
+ * @param limit total number of entries requested
+ * @return JSON document
+ */
+ @RequestMapping(method = RequestMethod.GET, value = "/{region}", produces = MediaType.APPLICATION_JSON_VALUE)
+ @ApiOperation(
+ value = "read all data for region",
+ notes = "Read all data for region. Use limit param to get fixed or limited number of entries.",
+ response = void.class
+ )
+ @ApiResponses( {
+ @ApiResponse( code = 200, message = "OK."),
+ @ApiResponse( code = 400, message = "Bad request." ),
+ @ApiResponse( code = 404, message = "Region does not exist." ),
+ @ApiResponse( code = 500, message = "GemFire throws an error or exception.")
+ } )
+ public ResponseEntity<?> read(@PathVariable("region") String region,
+ @RequestParam(value = "limit", defaultValue = DEFAULT_GETALL_RESULT_LIMIT) final String limit) {
+
+ if(logger.isDebugEnabled()){
+ logger.debug("Reading all data in Region ({})...", region);
+ }
+ region = decode(region);
+
++ final HttpHeaders headers = new HttpHeaders();
++
++ //Do request(Pre) authorization if security is enabled.
++ if(AuthorizationProvider.isSecurityEnabled()){
++ setAuthTokenHeader(headers);
++ AuthorizationProvider.init();
++ try{
++ AuthorizationProvider.getAllAuthorize(region, getRegion(region).keySet(), null);
++ }catch(NotAuthorizedException nae) {
++ return new ResponseEntity<String>(headers, HttpStatus.UNAUTHORIZED);
++ }
++ }
++
+ Map<Object, Object> valueObjs = null;
+ final RegionData<Object> data = new RegionData<Object>(region);
-
- final HttpHeaders headers = new HttpHeaders();
++
+ String keyList = null;
+ int regionSize = getRegion(region).size();
+ List<Object> keys = new ArrayList<Object>(regionSize);
+ List<Object> values = new ArrayList<Object>(regionSize);
+
+ for (Map.Entry<Object, Object> entry : getValues(region).entrySet() ) {
++ //Do post authorization if security is enabled.
++ if(AuthorizationProvider.isSecurityEnabled()){
++ try{
++ AuthorizationProvider.getAuthorizePP(region, entry.getKey(), entry.getValue());
++ }catch(NotAuthorizedException nae) {
++ //Sending UNAUTHORIZED response, if any one of the key has UNAUTHORIZED access configured.
++ return new ResponseEntity<String>(headers, HttpStatus.UNAUTHORIZED);
++ }
++ }
++
+ Object value = entry.getValue();
+ if (value != null) {
+ keys.add(entry.getKey());
+ values.add(value);
+ }
+ }
+
+ if ("ALL".equalsIgnoreCase(limit) ) {
+ data.add(values);
+ keyList = StringUtils.collectionToDelimitedString(keys, ",");
+ } else {
+ try {
+ int maxLimit = Integer.valueOf(limit);
+ if(maxLimit < 0){
+ String errorMessage = String.format("Negative limit param (%1$s) is not valid!", maxLimit);
+ return new ResponseEntity<String>(
- convertErrorAsJson(errorMessage), HttpStatus.BAD_REQUEST);
++ convertErrorAsJson(errorMessage), headers, HttpStatus.BAD_REQUEST);
+ }
+
+ int mapSize = keys.size();
+ if (maxLimit > mapSize) {
+ maxLimit = mapSize;
+ }
+ data.add(values.subList(0, maxLimit));
+
+ keyList = StringUtils.collectionToDelimitedString(
+ keys.subList(0, maxLimit), ",");
+
+ } catch (NumberFormatException e) {
+ // limit param is not specified in proper format. set the HTTPHeader
+ // for BAD_REQUEST
+ String errorMessage = String.format("limit param (%1$s) is not valid!", limit);
+ return new ResponseEntity<String>(
- convertErrorAsJson(errorMessage), HttpStatus.BAD_REQUEST);
++ convertErrorAsJson(errorMessage), headers, HttpStatus.BAD_REQUEST);
+ }
+ }
+
+ headers.set("Content-Location", toUri(region, keyList).toASCIIString() );
+ return new ResponseEntity<RegionData<?>>(data, headers, HttpStatus.OK);
+ }
+
+ /**
+ * Reading data for set of keys
+ * @param region gemfire region name
+ * @param keys string containing comma seperated keys
+ * @return JSON document
+ */
+ @RequestMapping(method = RequestMethod.GET, value = "/{region}/{keys}",
+ produces = MediaType.APPLICATION_JSON_VALUE)
+ @ApiOperation(
+ value = "read data for specific keys",
+ notes = "Read data for specific set of keys in region.",
+ response = void.class
+ )
+ @ApiResponses( {
+ @ApiResponse( code = 200, message = "OK."),
+ @ApiResponse( code = 400, message = "Bad Request."),
+ @ApiResponse( code = 404, message = "Region does not exist." ),
+ @ApiResponse( code = 500, message = "GemFire throws an error or exception.")
+ } )
+ public ResponseEntity<?> read(
+ @PathVariable("region") String region,
+ @PathVariable("keys") final String[] keys,
+ @RequestParam(value = "ignoreMissingKey", required = false ) final String ignoreMissingKey) {
+
+ if(logger.isDebugEnabled()){
+ logger.debug("Reading data for keys ({}) in Region ({})",
+ ArrayUtils.toString(keys), region);
+ }
+
+ final HttpHeaders headers = new HttpHeaders();
+ region = decode(region);
+
++ //Do request(Pre) authorization if security is enabled.
++ if(AuthorizationProvider.isSecurityEnabled()){
++ setAuthTokenHeader(headers);
++ AuthorizationProvider.init();
++ try{
++ AuthorizationProvider.getAuthorize(region, keys, null);
++ } catch(NotAuthorizedException nae) {
++ return new ResponseEntity<String>(headers, HttpStatus.UNAUTHORIZED);
++ }
++ }
++
+ if (keys.length == 1) {
+ /* GET op on single key */
+ Object value = getValue(region, keys[0]);
+ //if region.get(K) return null (i.e INVLD or TOMBSTONE case) We consider 404, NOT Found case
+ if(value == null) {
+ throw new ResourceNotFoundException(String.format("Key (%1$s) does not exist for region (%2$s) in cache!", keys[0], region));
+ }
+
++ //Do post authorization if security is enabled.
++ if(AuthorizationProvider.isSecurityEnabled()){
++ try{
++ AuthorizationProvider.getAuthorizePP(region, keys[0], value);
++ }catch(NotAuthorizedException nae) {
++ return new ResponseEntity<String>(headers, HttpStatus.UNAUTHORIZED);
++ }
++ }
++
+ final RegionEntryData<Object> data = new RegionEntryData<Object>(region);
+ headers.set("Content-Location", toUri(region, keys[0]).toASCIIString());
+ data.add(value);
+ return new ResponseEntity<RegionData<?>>(data, headers, HttpStatus.OK);
+
+ } else {
+ //fail fast for the case where ignoreMissingKey param is not specified correctly.
+ if (ignoreMissingKey != null
+ && !(ignoreMissingKey.equalsIgnoreCase("true") || ignoreMissingKey.equalsIgnoreCase("false"))){
+ String errorMessage = String.format("ignoreMissingKey param (%1$s) is not valid. valid usage is ignoreMissingKey=true!", ignoreMissingKey);
+ return new ResponseEntity<String>(
- convertErrorAsJson(errorMessage), HttpStatus.BAD_REQUEST);
++ convertErrorAsJson(errorMessage), headers, HttpStatus.BAD_REQUEST);
+ }
+
+ if(!("true".equalsIgnoreCase(ignoreMissingKey))) {
+ List<String> unknownKeys = checkForMultipleKeysExist(region, keys);
+ if(unknownKeys.size() > 0) {
+ String unknownKeysAsStr = StringUtils.collectionToDelimitedString(unknownKeys, ",");
+ String erroString = String.format("Requested keys (%1$s) not exist in region (%2$s)", StringUtils.collectionToDelimitedString(unknownKeys, ","), region);
+ return new ResponseEntity<String>(convertErrorAsJson(erroString), headers, HttpStatus.BAD_REQUEST);
+ }
+ }
+
+ final Map<Object, Object> valueObjs = getValues(region, keys);
-
++
++ //Do post authorization if security is enabled.
++ if(AuthorizationProvider.isSecurityEnabled()){
++ for (Map.Entry<Object, Object> entry : valueObjs.entrySet() ) {
++ try{
++ AuthorizationProvider.getAuthorizePP(region, entry.getKey(), entry.getValue());
++ }catch(NotAuthorizedException nae) {
++ return new ResponseEntity<String>(headers, HttpStatus.UNAUTHORIZED);
++ }
++ }
++ }
++
+ // Do we need to remove null values from Map..?
+ // To Remove null value entries from map.
+ // valueObjs.values().removeAll(Collections.singleton(null));
+
+ //currently we are not removing keys having value null from the result.
+ String keyList = StringUtils.collectionToDelimitedString(valueObjs.keySet(), ",");
+ headers.set("Content-Location", toUri(region, keyList).toASCIIString() );
+ final RegionData<Object> data = new RegionData<Object>(region);
+ data.add(valueObjs.values());
+ return new ResponseEntity<RegionData<?>>(data, headers, HttpStatus.OK);
+ }
+ }
+
+ /**
+ * Update data for a key or set of keys
+ * @param region gemfire data region
+ * @param keys keys for which update operation is requested
+ * @param opValue type of update (put, replace, cas etc)
+ * @param json new data for the key(s)
+ * @return JSON document
+ */
+ @RequestMapping(method = RequestMethod.PUT, value = "/{region}/{keys}",
+ consumes = { MediaType.APPLICATION_JSON_VALUE },
+ produces = { MediaType.APPLICATION_JSON_VALUE })
+ @ApiOperation(
+ value = "update data for key",
+ notes = "Update or insert (put) data for key in region." +
+ "op=REPLACE, update (replace) data with key if and only if the key exists in region" +
+ "op=CAS update (compare-and-set) value having key with a new value if and only if the \"@old\" value sent matches the current value for the key in region",
+ response = void.class
+ )
+ @ApiResponses( {
+ @ApiResponse( code = 200, message = "OK."),
+ @ApiResponse( code = 400, message = "Bad Request."),
+ @ApiResponse( code = 404, message = "Region does not exist or if key is not mapped to some value for REPLACE or CAS."),
+ @ApiResponse( code = 409, message = "For CAS, @old value does not match to the current value in region" ),
+ @ApiResponse( code = 500, message = "GemFire throws an error or exception.")
+ } )
+ public ResponseEntity<?> update(@PathVariable("region") String region,
+ @PathVariable("keys") final String[] keys,
+ @RequestParam(value = "op", defaultValue = "PUT") final String opValue,
+ @RequestBody final String json) {
+
+ if(logger.isDebugEnabled()){
+ logger.debug("updating key(s) for region ({}) ", region);
+ }
++
+ region = decode(region);
++ HttpHeaders headers = new HttpHeaders();
++
++ //Do request(Pre) authorization if security is enabled.
++ if(AuthorizationProvider.isSecurityEnabled()){
++ setAuthTokenHeader(headers);
++ AuthorizationProvider.init();
++ try{
++ //TODO: add isJson type in OperationContext
++ if(keys.length > 1){
++ AuthorizationProvider.putAllAuthorize(region, json, null);
++ }else {
++ //TODO: add isJson type in OperationContext
++ AuthorizationProvider.putAuthorize(region, keys[0], json, false /*isObject*/, /*isJson,*/ null, PutOperationContext.UPDATE);
++ }
++ }catch(NotAuthorizedException nae) {
++ return new ResponseEntity<String>(headers, HttpStatus.UNAUTHORIZED);
++ }
++ }
+
+ if(keys.length > 1){
+ //putAll case
- return updateMultipleKeys(region, keys, json);
++ return updateMultipleKeys(region, keys, json, headers);
+ } else {
+ //put case
- return updateSingleKey(region, keys[0], json, opValue);
++ return updateSingleKey(region, keys[0], json, opValue, headers);
+ }
+ }
+
+ @RequestMapping(method = RequestMethod.HEAD, value = "/{region}", produces = MediaType.APPLICATION_JSON_VALUE)
+ @ApiOperation(
+ value = "Get total number of entries",
+ notes = "Get total number of entries into the specified region",
+ response = void.class
+ )
+ @ApiResponses( {
+ @ApiResponse( code = 200, message = "OK."),
+ @ApiResponse( code = 400, message = "Bad request." ),
+ @ApiResponse( code = 404, message = "Region does not exist." ),
+ @ApiResponse( code = 500, message = "GemFire throws an error or exception.")
+ } )
+ public ResponseEntity<?> size(@PathVariable("region") String region) {
+
+ if(logger.isDebugEnabled()){
+ logger.debug("Determining the number of entries in Region ({})...", region);
+ }
+ region = decode(region);
-
- final HttpHeaders headers = new HttpHeaders();
++ //Not Authorized at REST APIs level as even client-server does not provide authz
+
++ final HttpHeaders headers = new HttpHeaders();
+ headers.set("Resource-Count", String.valueOf(getRegion(region).size()) );
+ return new ResponseEntity<RegionData<?>>(headers, HttpStatus.OK);
+ }
+
+ }
http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/5c01d5f4/geode-web-api/src/main/java/com/gemstone/gemfire/rest/internal/web/controllers/QueryAccessController.java
----------------------------------------------------------------------
diff --cc geode-web-api/src/main/java/com/gemstone/gemfire/rest/internal/web/controllers/QueryAccessController.java
index 0000000,ef034cb..460ed6c
mode 000000,100644..100644
--- a/geode-web-api/src/main/java/com/gemstone/gemfire/rest/internal/web/controllers/QueryAccessController.java
+++ b/geode-web-api/src/main/java/com/gemstone/gemfire/rest/internal/web/controllers/QueryAccessController.java
@@@ -1,0 -1,354 +1,459 @@@
+ /*
+ * 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 com.gemstone.gemfire.rest.internal.web.controllers;
+
++import java.util.Set;
+ import java.util.concurrent.ConcurrentHashMap;
+
+ import org.apache.logging.log4j.Logger;
+ import org.springframework.http.HttpHeaders;
+ import org.springframework.http.HttpStatus;
+ import org.springframework.http.MediaType;
+ import org.springframework.http.ResponseEntity;
+ import org.springframework.stereotype.Controller;
+ import org.springframework.web.bind.annotation.PathVariable;
+ import org.springframework.web.bind.annotation.RequestBody;
+ import org.springframework.web.bind.annotation.RequestMapping;
+ import org.springframework.web.bind.annotation.RequestMethod;
+ import org.springframework.web.bind.annotation.RequestParam;
+ import org.springframework.web.bind.annotation.ResponseBody;
+ import org.springframework.web.bind.annotation.ResponseStatus;
+
+ import com.gemstone.gemfire.cache.Region;
++import com.gemstone.gemfire.cache.operations.OperationContext;
++import com.gemstone.gemfire.cache.operations.OperationContext.OperationCode;
++import com.gemstone.gemfire.cache.operations.PutOperationContext;
++import com.gemstone.gemfire.cache.operations.QueryOperationContext;
+ import com.gemstone.gemfire.cache.query.FunctionDomainException;
+ import com.gemstone.gemfire.cache.query.NameResolutionException;
+ import com.gemstone.gemfire.cache.query.Query;
+ import com.gemstone.gemfire.cache.query.QueryExecutionLowMemoryException;
+ import com.gemstone.gemfire.cache.query.QueryExecutionTimeoutException;
+ import com.gemstone.gemfire.cache.query.QueryInvalidException;
+ import com.gemstone.gemfire.cache.query.QueryInvocationTargetException;
+ import com.gemstone.gemfire.cache.query.TypeMismatchException;
+ import com.gemstone.gemfire.cache.query.internal.DefaultQuery;
+ import com.gemstone.gemfire.internal.logging.LogService;
+ import com.gemstone.gemfire.rest.internal.web.exception.GemfireRestException;
+ import com.gemstone.gemfire.rest.internal.web.exception.ResourceNotFoundException;
++import com.gemstone.gemfire.rest.internal.web.security.AuthorizationProvider;
+ import com.gemstone.gemfire.rest.internal.web.util.JSONUtils;
+ import com.gemstone.gemfire.rest.internal.web.util.ValidationUtils;
++import com.gemstone.gemfire.security.NotAuthorizedException;
+ import com.wordnik.swagger.annotations.Api;
+ import com.wordnik.swagger.annotations.ApiOperation;
+ import com.wordnik.swagger.annotations.ApiResponse;
+ import com.wordnik.swagger.annotations.ApiResponses;
+
+ /**
+ * The QueryingController class serves all HTTP REST requests related to the gemfire querying
+ * <p/>
+ * @author Nilkanth Patel, john blum
+ * @see org.springframework.stereotype.Controller
+ * @since 8.0
+ */
+
+ @Controller("queryController")
+ @Api(value = "queries", description = "Rest api for gemfire query execution", produces = MediaType.APPLICATION_JSON_VALUE)
+ @RequestMapping(QueryAccessController.REST_API_VERSION + "/queries")
+ @SuppressWarnings("unused")
+ public class QueryAccessController extends AbstractBaseController {
+
+ private static final Logger logger = LogService.getLogger();
+
+ protected static final String PARAMETERIZED_QUERIES_REGION = "__ParameterizedQueries__";
+
+ private final ConcurrentHashMap<String, DefaultQuery> compiledQueries = new ConcurrentHashMap<String, DefaultQuery>();
+
+ // Constant String value indicating the version of the REST API.
+ protected static final String REST_API_VERSION = "/v1";
+
+ /**
+ * Gets the version of the REST API implemented by this @Controller.
+ * <p/>
+ * @return a String indicating the REST API version.
+ */
+ @Override
+ protected String getRestApiVersion() {
+ return REST_API_VERSION;
+ }
+
+ /**
+ * list all parameterized Queries created in a Gemfire data node
+ * @return result as a JSON document.
+ */
+ @RequestMapping(method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE })
+ @ApiOperation(
+ value = "list all parameterized queries",
+ notes = "List all parameterized queries by id/name",
+ response = void.class
+ )
+ @ApiResponses( {
+ @ApiResponse( code = 200, message = "OK." ),
+ @ApiResponse( code = 500, message = "if GemFire throws an error or exception" )
+ } )
+ @ResponseBody
+ @ResponseStatus(HttpStatus.OK)
+ public ResponseEntity<?> list() {
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("Listing all parameterized Queries in GemFire...");
+ }
++
++ final HttpHeaders headers = new HttpHeaders();
++ headers.setLocation(toUri("queries"));
+
- final Region<String, String> parameterizedQueryRegion = getQueryStore(PARAMETERIZED_QUERIES_REGION);
++ //Do request(Pre) authorization if security is enabled.
++ if(AuthorizationProvider.isSecurityEnabled()){
++ setAuthTokenHeader(headers);
++ AuthorizationProvider.init();
++ try{
++ AuthorizationProvider.listQueriesAuthorize(OperationCode.LIST, true, "LIST_QUERIES");
++ }catch(NotAuthorizedException nae) {
++ return new ResponseEntity<String>(headers, HttpStatus.UNAUTHORIZED);
++ }
++ }
+
++ final Region<String, String> parameterizedQueryRegion = getQueryStore(PARAMETERIZED_QUERIES_REGION);
+ String queryListAsJson = JSONUtils.formulateJsonForListQueriesCall(parameterizedQueryRegion);
- final HttpHeaders headers = new HttpHeaders();
- headers.setLocation(toUri("queries"));
++
+ return new ResponseEntity<String>(queryListAsJson, headers, HttpStatus.OK);
+ }
+
+ /**
+ * Create a named, parameterized Query
+ * @param queryId uniquely identify the query
+ * @param oqlInUrl OQL query string specified in a request URL
+ * @param oqlInBody OQL query string specified in a request body
+ * @return result as a JSON document.
+ */
+ @RequestMapping(method = RequestMethod.POST)
+ @ApiOperation(
+ value = "create a parameterized Query",
+ notes = "Prepare the specified parameterized query and assign the corresponding ID for lookup",
+ response = void.class
+ )
+ @ApiResponses( {
+ @ApiResponse( code = 201, message = "Successfully created." ),
+ @ApiResponse( code = 409, message = "QueryId already assigned to other query." ),
+ @ApiResponse( code = 500, message = "GemFire throws an error or exception." )
+ } )
+ public ResponseEntity<?> create(@RequestParam("id") final String queryId,
+ @RequestParam(value = "q", required = false) String oqlInUrl,
+ @RequestBody(required = false) final String oqlInBody)
+ {
+ final String oqlStatement = validateQuery(oqlInUrl, oqlInBody);
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("Creating a named, parameterized Query ({}) with ID ({})...", oqlStatement, queryId);
+ }
-
- // store the compiled OQL statement with 'queryId' as the Key into the hidden, ParameterizedQueries Region...
- final String existingOql = createNamedQuery(PARAMETERIZED_QUERIES_REGION, queryId, oqlStatement);
-
++
+ final HttpHeaders headers = new HttpHeaders();
+ headers.setLocation(toUri("queries", queryId));
-
++
++
++ //Do request(Pre) authorization if security is enabled.
++ if(AuthorizationProvider.isSecurityEnabled()){
++ setAuthTokenHeader(headers);
++ AuthorizationProvider.init();
++ try{
++ AuthorizationProvider.createQueryAuthorize(OperationCode.CREATE_QUERY, true, "CREATE_QUERY", queryId, oqlStatement);
++ }catch(NotAuthorizedException nae) {
++ return new ResponseEntity<String>(headers, HttpStatus.UNAUTHORIZED);
++ }
++ }
++
++ // store the compiled OQL statement with 'queryId' as the Key into the hidden, ParameterizedQueries Region...
++ final String existingOql = createNamedQuery(PARAMETERIZED_QUERIES_REGION, queryId, oqlStatement);
++
+ if (existingOql != null) {
+ headers.setContentType(MediaType.APPLICATION_JSON);
+ return new ResponseEntity<String>(JSONUtils.formulateJsonForExistingQuery(queryId, existingOql), headers, HttpStatus.CONFLICT);
+ } else {
+ return new ResponseEntity<String>(headers, HttpStatus.CREATED);
+ }
+ }
+
+ /**
+ * Run an adhoc Query specified in a query string
+ * @param oql OQL query string to be executed
+ * @return query result as a JSON document
+ */
+ @RequestMapping(method = RequestMethod.GET, value = "/adhoc", produces = { MediaType.APPLICATION_JSON_VALUE })
+ @ApiOperation(
+ value = "run an adhoc query",
+ notes = "Run an unnamed (unidentified), ad-hoc query passed as a URL parameter",
+ response = void.class
+ )
+ @ApiResponses( {
+ @ApiResponse( code = 200, message = "OK." ),
+ @ApiResponse( code = 500, message = "GemFire throws an error or exception" )
+ } )
+ @ResponseBody
+ @ResponseStatus(HttpStatus.OK)
+ public ResponseEntity<String> runAdhocQuery(@RequestParam("q") String oql) {
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("Running an adhoc Query ({})...", oql);
+ }
++
++ HttpHeaders headers = new HttpHeaders();
+ oql = decode(oql);
+ final Query query = getQueryService().newQuery(oql);
+
++ Set regionNames = ((DefaultQuery)query).getRegionsInQuery(null);
++
++ //Do request(Pre) authorization if security is enabled.
++ QueryOperationContext queryAuthzContext = null;
++ if(AuthorizationProvider.isSecurityEnabled()){
++ setAuthTokenHeader(headers);
++ AuthorizationProvider.init();
++ try{
++ queryAuthzContext = AuthorizationProvider.queryAuthorize(oql, regionNames, null);
++ }catch(NotAuthorizedException nae) {
++ return new ResponseEntity<String>(headers, HttpStatus.UNAUTHORIZED);
++ }
++ }
++
+ // NOTE Query.execute throws many checked Exceptions; let the BaseControllerAdvice Exception handlers catch
+ // and handle the Exceptions appropriately (500 Server Error)!
+ try {
+ Object queryResult = query.execute();
- return processQueryResponse(queryResult, "adhoc?q=" + oql);
++
++ //Post authorization
++ if(AuthorizationProvider.isSecurityEnabled()){
++ queryAuthzContext = AuthorizationProvider.queryAuthorizePP(oql, regionNames, queryResult, queryAuthzContext, null);
++ if(queryAuthzContext != null){
++ queryResult = queryAuthzContext.getQueryResult();
++ }
++ }
++ return processQueryResponse(queryResult, "adhoc?q=" + oql, headers);
+ } catch (FunctionDomainException fde) {
+ throw new GemfireRestException("A function was applied to a parameter that is improper for that function!", fde);
+ } catch (TypeMismatchException tme) {
+ throw new GemfireRestException("Bind parameter is not of the expected type!", tme);
+ }catch (NameResolutionException nre) {
+ throw new GemfireRestException("Name in the query cannot be resolved!", nre);
+ }catch (IllegalArgumentException iae) {
+ throw new GemfireRestException(" The number of bound parameters does not match the number of placeholders!", iae);
+ }catch (IllegalStateException ise) {
+ throw new GemfireRestException("Query is not permitted on this type of region!", ise);
+ }catch (QueryExecutionTimeoutException qete) {
+ throw new GemfireRestException("Query execution time is exceeded max query execution time (gemfire.Cache.MAX_QUERY_EXECUTION_TIME) configured! ", qete);
+ }catch (QueryInvocationTargetException qite) {
+ throw new GemfireRestException("Data referenced in from clause is not available for querying!", qite);
+ }catch (QueryExecutionLowMemoryException qelme) {
+ throw new GemfireRestException("Query execution gets canceled due to low memory conditions and the resource manager critical heap percentage has been set!", qelme);
+ }
+ catch (Exception e) {
+ throw new GemfireRestException("Server has encountered while executing Adhoc query!", e);
+ }
+ }
+
+ /**
+ * Run named parameterized Query with ID
+ * @param queryId id of the OQL string
+ * @param arguments query bind params required while executing query
+ * @return query result as a JSON document
+ */
+ @RequestMapping(method = RequestMethod.POST, value = "/{query}", produces = {MediaType.APPLICATION_JSON_VALUE})
+ @ApiOperation(
+ value = "run parameterized query",
+ notes = "run the specified named query passing in scalar values for query parameters in the GemFire cluster",
+ response = void.class
+ )
+ @ApiResponses( {
+ @ApiResponse( code = 200, message = "Query successfully executed." ),
+ @ApiResponse( code = 400, message = "Query bind params specified as JSON document in the request body is invalid" ),
+ @ApiResponse( code = 500, message = "GemFire throws an error or exception" )
+ } )
+ @ResponseBody
+ @ResponseStatus(HttpStatus.OK)
+ public ResponseEntity<String> runNamedQuery(@PathVariable("query") String queryId,
+ @RequestBody String arguments)
+ {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Running named Query with ID ({})...", queryId);
+ }
+ queryId = decode(queryId);
++ HttpHeaders headers = new HttpHeaders();
+
+ if (arguments != null) {
+ // Its a compiled query.
+
+ //Convert arguments into Object[]
+ Object args[] = jsonToObjectArray(arguments);
+
++ final String oql = getValue(PARAMETERIZED_QUERIES_REGION, queryId);
+ Query compiledQuery = compiledQueries.get(queryId);
+ if (compiledQuery == null) {
+ // This is first time the query is seen by this server.
- final String oql = getValue(PARAMETERIZED_QUERIES_REGION, queryId);
-
+ ValidationUtils.returnValueThrowOnNull(oql, new ResourceNotFoundException(
+ String.format("No Query with ID (%1$s) was found!", queryId)));
+ try {
+ compiledQuery = getQueryService().newQuery(oql);
+ } catch (QueryInvalidException qie) {
+ throw new GemfireRestException("Syntax of the OQL queryString is invalid!", qie);
+ }
+ compiledQueries.putIfAbsent(queryId, (DefaultQuery)compiledQuery);
+ }
- // NOTE Query.execute throws many checked Exceptions; let the BaseControllerAdvice Exception handlers catch
++
++ Set regionNames = ((DefaultQuery)compiledQuery).getRegionsInQuery(args);
++
++ //Do request(Pre) authorization if security is enabled.
++ QueryOperationContext queryAuthzContext = null;
++ if(AuthorizationProvider.isSecurityEnabled()){
++ setAuthTokenHeader(headers);
++ AuthorizationProvider.init();
++ try{
++ queryAuthzContext= AuthorizationProvider.queryAuthorize(oql, regionNames, args);
++ }catch(NotAuthorizedException nae) {
++ return new ResponseEntity<String>(headers, HttpStatus.UNAUTHORIZED);
++ }
++ }
++
++ // NOTE Query.execute throws many checked Exceptions; let the BaseControllerAdvice Exception handlers catch
+ // and handle the Exceptions appropriately (500 Server Error)!
+ try {
+ Object queryResult = compiledQuery.execute(args);
- return processQueryResponse(queryResult, queryId);
++
++ //Post authorization
++ if(AuthorizationProvider.isSecurityEnabled()){
++ queryAuthzContext = AuthorizationProvider.queryAuthorizePP(oql, regionNames, queryResult, queryAuthzContext, args);
++ if(queryAuthzContext != null){
++ queryResult = queryAuthzContext.getQueryResult();
++ }
++ }
++
++ return processQueryResponse(queryResult, queryId, headers);
+ } catch (FunctionDomainException fde) {
+ throw new GemfireRestException("A function was applied to a parameter that is improper for that function!", fde);
+ } catch (TypeMismatchException tme) {
+ throw new GemfireRestException("Bind parameter is not of the expected type!", tme);
+ } catch (NameResolutionException nre) {
+ throw new GemfireRestException("Name in the query cannot be resolved!", nre);
+ } catch (IllegalArgumentException iae) {
+ throw new GemfireRestException(" The number of bound parameters does not match the number of placeholders!", iae);
+ } catch (IllegalStateException ise) {
+ throw new GemfireRestException("Query is not permitted on this type of region!", ise);
+ } catch (QueryExecutionTimeoutException qete) {
+ throw new GemfireRestException("Query execution time is exceeded max query execution time (gemfire.Cache.MAX_QUERY_EXECUTION_TIME) configured!", qete);
+ } catch (QueryInvocationTargetException qite) {
+ throw new GemfireRestException("Data referenced in from clause is not available for querying!", qite);
+ } catch (QueryExecutionLowMemoryException qelme) {
+ throw new GemfireRestException("Query gets canceled due to low memory conditions and the resource manager critical heap percentage has been set!", qelme);
+ } catch (Exception e) {
+ throw new GemfireRestException("Error encountered while executing named query!", e);
+ }
+ } else {
+ throw new GemfireRestException(" Bind params either not specified or not processed properly by the server!");
+ }
+ }
+
+ /**
+ * Update named, parameterized Query
+ * @param queryId uniquely identify the query
+ * @param oqlInUrl OQL query string specified in a request URL
+ * @param oqlInBody OQL query string specified in a request body
+ */
+ @RequestMapping(method = RequestMethod.PUT, value = "/{query}")
+ @ApiOperation(
+ value = "update parameterized query",
+ notes = "Update named, parameterized query by ID",
+ response = void.class
+ )
+ @ApiResponses( {
+ @ApiResponse( code = 200, message = "Updated successfully." ),
+ @ApiResponse( code = 404, message = "queryId does not exist." ),
+ @ApiResponse( code = 500, message = "GemFire throws an error or exception." )
+ } )
+ public ResponseEntity<?> update( @PathVariable("query") final String queryId,
+ @RequestParam(value = "q", required = false) String oqlInUrl,
+ @RequestBody(required = false) final String oqlInBody) {
+
+ final String oqlStatement = validateQuery(oqlInUrl, oqlInBody);
-
++
+ if (logger.isDebugEnabled()) {
+ logger.debug("Updating a named, parameterized Query ({}) with ID ({})...", oqlStatement, queryId);
+ }
-
++
++ HttpHeaders headers = new HttpHeaders();
++
++ //Do request(Pre) authorization if security is enabled.
++ if(AuthorizationProvider.isSecurityEnabled()){
++ setAuthTokenHeader(headers);
++ AuthorizationProvider.init();
++ try{
++ AuthorizationProvider.updateQueryAuthorize(OperationCode.UPDATE_QUERY, true, "UPDATE_QUERY", queryId, oqlStatement);
++ }catch(NotAuthorizedException nae) {
++ return new ResponseEntity<String>(headers, HttpStatus.UNAUTHORIZED);
++ }
++ }
++
+ // update the OQL statement with 'queryId' as the Key into the hidden, ParameterizedQueries Region...
+ checkForQueryIdExist(PARAMETERIZED_QUERIES_REGION, queryId);
+ updateNamedQuery(PARAMETERIZED_QUERIES_REGION, queryId, oqlStatement);
+ compiledQueries.remove(queryId);
+
- return new ResponseEntity<Object>(HttpStatus.OK);
++ return new ResponseEntity<Object>(headers, HttpStatus.OK);
+ }
+
+ //delete named, parameterized query
+ /**
+ * Delete named, parameterized Query
+ * @param queryId uniquely identify the query to be deleted
+ */
+ @RequestMapping(method = RequestMethod.DELETE, value = "/{query}")
+ @ApiOperation(
+ value = "delete parameterized query",
+ notes = "delete named, parameterized query by ID",
+ response = void.class
+ )
+ @ApiResponses( {
+ @ApiResponse( code = 200, message = "Deleted successfully." ),
+ @ApiResponse( code = 404, message = "queryId does not exist." ),
+ @ApiResponse( code = 500, message = "GemFire throws an error or exception" )
+ } )
+ public ResponseEntity<?> delete(@PathVariable("query") final String queryId) {
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("Deleting a named, parameterized Query with ID ({}).", queryId);
+ }
+
++ HttpHeaders headers = new HttpHeaders();
++
++ //Do request(Pre) authorization if security is enabled.
++ if(AuthorizationProvider.isSecurityEnabled()){
++ setAuthTokenHeader(headers);
++ AuthorizationProvider.init();
++ try{
++ AuthorizationProvider.deleteQueryAuthorize(OperationCode.DELETE_QUERY, true, "DELETE_QUERY", queryId);
++ }catch(NotAuthorizedException nae) {
++ return new ResponseEntity<String>(headers, HttpStatus.UNAUTHORIZED);
++ }
++ }
++
+ //delete the OQL statement with 'queryId' as the Key into the hidden,
+ // ParameterizedQueries Region...
+ deleteNamedQuery(PARAMETERIZED_QUERIES_REGION, queryId);
+ compiledQueries.remove(queryId);
- return new ResponseEntity<Object>(HttpStatus.OK);
++ return new ResponseEntity<Object>(headers, HttpStatus.OK);
+ }
+
+ }
+
http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/5c01d5f4/geode-web-api/src/main/webapp/WEB-INF/web.xml
----------------------------------------------------------------------
diff --cc geode-web-api/src/main/webapp/WEB-INF/web.xml
index 0000000,ad354f1..56a1c85
mode 000000,100644..100644
--- a/geode-web-api/src/main/webapp/WEB-INF/web.xml
+++ b/geode-web-api/src/main/webapp/WEB-INF/web.xml
@@@ -1,0 -1,65 +1,75 @@@
+ <?xml version="1.0" encoding="UTF-8"?>
+ <!--
+ 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.
+ -->
+ <web-app xmlns="http://java.sun.com/xml/ns/javaee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+ version="3.0">
+
+ <display-name>GemFire Developer REST API</display-name>
+
+ <description>
+ Web deployment descriptor declaring the developer REST API for GemFire.
+ </description>
+
+ <!-- context-param>
+ <param-name>contextConfigLocation</param-name>
+ <param-value>/META-INF/cache-config.xml</param-value>
+ </context-param -->
+
+ <filter>
+ <filter-name>httpPutFilter</filter-name>
+ <filter-class>org.springframework.web.filter.HttpPutFormContentFilter</filter-class>
+ <async-supported>true</async-supported>
+ </filter>
+
+ <filter-mapping>
+ <filter-name>httpPutFilter</filter-name>
+ <url-pattern>/*</url-pattern>
+ </filter-mapping>
+
+ <!-- listener>
+ <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
+ </listener-->
+
+ <servlet>
+ <description>
+ The Spring DispatcherServlet (FrontController) handling all HTTP requests to the Developer REST API
+ GemFire Web Application.
+ </description>
+ <servlet-name>gemfire-api</servlet-name>
+ <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
+ <load-on-startup>1</load-on-startup>
+ <async-supported>true</async-supported>
+ </servlet>
+
+ <servlet-mapping>
+ <servlet-name>gemfire-api</servlet-name>
+ <url-pattern>/*</url-pattern>
+ </servlet-mapping>
+
++ <filter>
++ <filter-name>restRequestFilter</filter-name>
++ <filter-class>com.gemstone.gemfire.rest.internal.web.security.RestRequestFilter</filter-class>
++ </filter>
++
++ <filter-mapping>
++ <filter-name>restRequestFilter</filter-name>
++ <url-pattern>/*</url-pattern>
++ </filter-mapping>
++
+ </web-app>