/**
* 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.ambari.server.api.services;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.UriInfo;
import org.apache.ambari.server.api.handlers.RequestHandler;
import org.apache.ambari.server.api.predicate.InvalidQueryException;
import org.apache.ambari.server.api.predicate.PredicateCompiler;
import org.apache.ambari.server.api.predicate.QueryLexer;
import org.apache.ambari.server.api.query.render.Renderer;
import org.apache.ambari.server.api.resources.ResourceInstance;
import org.apache.ambari.server.controller.internal.PageRequestImpl;
import org.apache.ambari.server.controller.internal.SortRequestImpl;
import org.apache.ambari.server.controller.internal.TemporalInfoImpl;
import org.apache.ambari.server.controller.spi.PageRequest;
import org.apache.ambari.server.controller.spi.Predicate;
import org.apache.ambari.server.controller.spi.SortRequest;
import org.apache.ambari.server.controller.spi.SortRequestProperty;
import org.apache.ambari.server.controller.spi.TemporalInfo;
import org.apache.ambari.server.utils.RequestUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Request implementation.
*/
public abstract class BaseRequest implements Request {
/**
* URI information
*/
private UriInfo m_uriInfo;
/**
* Http headers
*/
private HttpHeaders m_headers;
/**
* Http Body
*/
private RequestBody m_body;
/**
* Remote address
*/
private String m_remoteAddress;
/**
* Query Predicate
*/
private Predicate m_predicate;
/**
* Associated resource definition
*/
private ResourceInstance m_resource;
/**
* Default page size for pagination request.
*/
public static final int DEFAULT_PAGE_SIZE = 20;
/**
* Page size property key
*/
public static final String PAGE_SIZE_PROPERTY_KEY = "Request_Info/max_results";
/**
* Sort order property key. (true - ASC , false - DESC)
*/
public static final String ASC_ORDER_PROPERTY_KEY = "Request_Info/asc_order";
/**
* Associated resource renderer.
* Will default to the default renderer if non is specified.
*/
private Renderer m_renderer;
/**
* Logger instance.
*/
private final static Logger LOG = LoggerFactory.getLogger(Request.class);
/**
* Constructor.
*
* @param headers http headers
* @param body http body
* @param uriInfo uri information
* @param resource associated resource definition
*
*/
public BaseRequest(HttpHeaders headers, RequestBody body, UriInfo uriInfo, ResourceInstance resource) {
m_headers = headers;
m_uriInfo = uriInfo;
m_resource = resource;
m_body = body;
m_remoteAddress = RequestUtils.getRemoteAddress();
}
@Override
public Result process() {
if (LOG.isDebugEnabled()) {
LOG.debug("Handling API Request: '" + getURI() + "'");
}
Result result;
try {
parseRenderer();
parseQueryPredicate();
result = getRequestHandler().handleRequest(this);
} catch (InvalidQueryException e) {
String message = "Unable to compile query predicate: " + e.getMessage();
LOG.error(message, e);
result = new ResultImpl(new ResultStatus(ResultStatus.STATUS.BAD_REQUEST, message));
} catch (IllegalArgumentException e) {
String message = "Invalid Request: " + e.getMessage();
LOG.error(message, e);
result = new ResultImpl(new ResultStatus(ResultStatus.STATUS.BAD_REQUEST, message));
}
if (! result.getStatus().isErrorState()) {
getResultPostProcessor().process(result);
}
return result;
}
@Override
public ResourceInstance getResource() {
return m_resource;
}
@Override
public String getURI() {
return m_uriInfo.getRequestUri().toASCIIString();
}
@Override
public int getAPIVersion() {
return 1;
}
@Override
public Predicate getQueryPredicate() {
return m_predicate;
}
@Override
public Map<String, TemporalInfo> getFields() {
Map<String, TemporalInfo> mapProperties;
String partialResponseFields = m_uriInfo.getQueryParameters().getFirst(QueryLexer.QUERY_FIELDS);
if (partialResponseFields == null) {
mapProperties = Collections.emptyMap();
} else {
Set<String> setMatches = new HashSet<>();
// Pattern basically splits a string using ',' as the deliminator unless ',' is between '[' and ']'.
// Actually, captures char sequences between ',' and all chars between '[' and ']' including ','.
Pattern re = Pattern.compile("[^,\\[]*?\\[[^\\]]*?\\]|[^,]+");
Matcher m = re.matcher(partialResponseFields);
while (m.find()){
for (int groupIdx = 0; groupIdx < m.groupCount() + 1; groupIdx++) {
setMatches.add(m.group(groupIdx));
}
}
mapProperties = new HashMap<>(setMatches.size());
for (String field : setMatches) {
TemporalInfo temporalInfo = null;
if (field.contains("[")) {
String[] temporalData = field.substring(field.indexOf('[') + 1,
field.indexOf(']')).split(",");
field = field.substring(0, field.indexOf('['));
long start = Long.parseLong(temporalData[0].trim());
long end = -1;
long step = -1;
if (temporalData.length >= 2) {
end = Long.parseLong(temporalData[1].trim());
if (temporalData.length == 3) {
step = Long.parseLong(temporalData[2].trim());
}
}
temporalInfo = new TemporalInfoImpl(start, end, step);
}
mapProperties.put(field, temporalInfo);
}
}
return mapProperties;
}
@Override
public Renderer getRenderer() {
return m_renderer;
}
@Override
public Map<String, List<String>> getHttpHeaders() {
return m_headers.getRequestHeaders();
}
@Override
public PageRequest getPageRequest() {
String pageSize = m_uriInfo.getQueryParameters().getFirst(QueryLexer.QUERY_PAGE_SIZE);
String from = m_uriInfo.getQueryParameters().getFirst(QueryLexer.QUERY_FROM);
String to = m_uriInfo.getQueryParameters().getFirst(QueryLexer.QUERY_TO);
if (pageSize == null && from == null && to == null) {
return null;
}
int offset = 0;
PageRequest.StartingPoint startingPoint;
// TODO : support other starting points
if (from != null) {
if(from.equals("start")) {
startingPoint = PageRequest.StartingPoint.Beginning;
} else {
offset = Integer.parseInt(from);
startingPoint = PageRequest.StartingPoint.OffsetStart;
}
} else if (to != null ) {
if (to.equals("end")) {
startingPoint = PageRequest.StartingPoint.End;
} else {
offset = Integer.parseInt(to);
startingPoint = PageRequest.StartingPoint.OffsetEnd;
}
} else {
startingPoint = PageRequest.StartingPoint.Beginning;
}
// TODO : support predicate and comparator
return new PageRequestImpl(startingPoint,
pageSize == null ? DEFAULT_PAGE_SIZE : Integer.valueOf(pageSize), offset, null, null);
}
@Override
public RequestBody getBody() {
return m_body;
}
/**
* Obtain the result post processor for the request.
*
* @return the result post processor
*/
protected ResultPostProcessor getResultPostProcessor() {
return m_renderer.getResultPostProcessor(this);
}
/**
* Obtain the predicate compiler which is used to compile the query string into
* a predicate.
*
* @return the predicate compiler
*/
protected PredicateCompiler getPredicateCompiler() {
return new PredicateCompiler();
}
/**
* Check to see if 'minimal_response=true' is specified in the query string.
*
* @return true if 'minimal_response=true' is specified, false otherwise
*/
private boolean isMinimal() {
String minimal = m_uriInfo.getQueryParameters().getFirst(QueryLexer.QUERY_MINIMAL);
return minimal != null && minimal.equalsIgnoreCase("true");
}
/**
* Parse the query string and compile it into a predicate.
* The query string may have already been extracted from the http body.
* If the query string didn't exist in the body use the query string in the URL.
*
* @throws InvalidQueryException if unable to parse a non-null query string into a predicate
*/
private void parseQueryPredicate() throws InvalidQueryException {
String queryString = m_body.getQueryString();
if (queryString == null) {
String uri = getURI();
int qsBegin = uri.indexOf("?");
queryString = (qsBegin == -1) ? null : uri.substring(qsBegin + 1);
}
if (queryString != null) {
try {
Collection<String> ignoredProperties = null;
switch (getRequestType()) {
case PUT:
ignoredProperties = m_resource.getResourceDefinition().getUpdateDirectives();
break;
case POST:
ignoredProperties = m_resource.getResourceDefinition().getCreateDirectives();
break;
case GET:
ignoredProperties = m_resource.getResourceDefinition().getReadDirectives();
break;
case DELETE:
ignoredProperties = m_resource.getResourceDefinition().getDeleteDirectives();
break;
default:
break;
}
m_predicate = (ignoredProperties == null)
? getPredicateCompiler().compile(URLDecoder.decode(queryString, "UTF-8"))
: getPredicateCompiler().compile(URLDecoder.decode(queryString, "UTF-8"), ignoredProperties);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Unable to decode URI: " + e, e);
}
}
}
/**
* Parse the query string for the {@link QueryLexer#QUERY_FORMAT} property and obtain
* a renderer from the associated resource definition based on this property value.
*/
private void parseRenderer() {
String rendererName = isMinimal() ? "minimal" :
m_uriInfo.getQueryParameters().getFirst(QueryLexer.QUERY_FORMAT);
m_renderer = m_resource.getResourceDefinition().
getRenderer(rendererName);
}
@Override
public SortRequest getSortRequest() {
String sortByParams = m_uriInfo.getQueryParameters().getFirst(QueryLexer.QUERY_SORT);
if (sortByParams != null && !sortByParams.isEmpty()) {
String[] params = sortByParams.split(",");
List<SortRequestProperty> properties = new ArrayList<>();
if (params.length > 0) {
for (String property : params) {
SortRequest.Order order = SortRequest.Order.ASC;
String propertyId = property;
int idx = property.indexOf(".");
if (idx != -1) {
order = SortRequest.Order.valueOf(property.substring(idx + 1).toUpperCase());
propertyId = property.substring(0, idx);
}
properties.add(new SortRequestProperty(propertyId, order));
}
}
return new SortRequestImpl(properties);
}
return null;
}
/**
* Obtain the underlying request handler for the request.
*
* @return the request handler
*/
protected abstract RequestHandler getRequestHandler();
@Override
public String getRemoteAddress() {
return m_remoteAddress;
}
}