/**
* Copyright 2004 The Apache Software Foundation
*
* Licensed 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.lucene.gdata.server;
import java.io.IOException;
import java.io.Reader;
import java.util.Enumeration;
import java.util.Map;
import java.util.StringTokenizer;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.gdata.search.config.IndexSchema;
import org.apache.lucene.gdata.search.query.QueryTranslator;
import org.apache.lucene.gdata.server.authentication.AuthenticationController;
import org.apache.lucene.gdata.server.registry.GDataServerRegistry;
import org.apache.lucene.gdata.server.registry.ProvidedService;
/**
* The GDataRequest Class wraps the incoming HttpServletRequest. Needed
* information coming with the HttpServletRequest can be accessed directly. It
* represents an abstraction on the plain HttpServletRequest. Every GData
* specific data coming from the client will be available and can be accessed
* via the GDataRequest.
* <p>
* GDataRequest instances will be passed to any action requested by the client.
* This class also holds the logic to retrieve important information like
* response format, the requested feed instance and query parameters.
*
* </p>
*
* @author Simon Willnauer
*
*/
/* this class might be extracted as an interface in later development */
public class GDataRequest {
private static final Log LOG = LogFactory.getLog(GDataRequest.class);
private static final String RESPONSE_FORMAT_PARAMETER = "alt";
private static final String RESPONSE_FORMAT_PARAMETER_RSS = "rss";
private static final String RESPONSE_FORMAT_PARAMETER_HTML = "html";
private static final int DEFAULT_ITEMS_PER_PAGE = 25;
private static final int DEFAULT_START_INDEX = 1;
private static final String START_INDEX_NEXT_PAGE_PARAMETER = "start-index";
private static final String ITEMS_PER_PAGE_PARAMETER = "max-results";
private String contextPath;
@SuppressWarnings("unused")
private static final String RESPONSE_FORMAT_PARAMETER_ATOM = "atom";
private static final String HTTP_HEADER_IF_MODIFIED_SINCE = "If-Modified-Since";
private static final String HTTP_HEADER_AUTH = "Authorization";
private static final Object CATEGORY_QUERY_INDICATOR = "-";
// Atom is the default response format
private OutputFormat responseFormat = OutputFormat.ATOM;
private final HttpServletRequest request;
private String feedId = null;
private String entryId = null;
private String service = null;
private ProvidedService configurator = null;
private boolean isSearchRequest = false;
private String entryVersion = null;
private GDataRequestType type;
private String categoryQuery;
private String translatedSearchQuery;
private boolean isFeedRequest = false;
/**
* Creates a new FeedRequest
*
* @param requst -
* the incoming HttpServletReqeust
* @param type -
* the request type
*
*/
public GDataRequest(final HttpServletRequest requst,
final GDataRequestType type) {
if (requst == null)
throw new IllegalArgumentException("request must not be null ");
if (type == null)
throw new IllegalArgumentException("request type must not be null ");
this.request = requst;
this.type = type;
}
/**
* Initialize the GDataRequest. This will initialize all needed values /
* attributes in this request.
*
* @throws GDataRequestException
*/
public void initializeRequest() throws GDataRequestException {
generateIdentificationProperties();
setOutputFormat();
try {
/*
* ExtensionProfile and the type is used for building the Entry /
* Feed Instances from an input stream or reader
*
*/
this.configurator = GDataServerRegistry.getRegistry()
.getProvidedService(this.service);
if (this.configurator == null)
throw new GDataRequestException(
"no Provided Service found for service id: "+this.service,GDataResponse.NOT_FOUND);
applyRequestParameter();
if(this.translatedSearchQuery != null)
this.isSearchRequest = true;
} catch(GDataRequestException ex){
throw ex;
} catch (Exception e) {
throw new GDataRequestException(
"failed to initialize GDataRequest -- "
+ e.getMessage(), e,GDataResponse.SERVER_ERROR);
}
}
@SuppressWarnings("unchecked")
private void applyRequestParameter() throws GDataRequestException{
IndexSchema schema = this.configurator.getIndexSchema();
try{
this.translatedSearchQuery = QueryTranslator.translateHttpSearchRequest(schema,this.request.getParameterMap(),this.categoryQuery);
}catch (Exception e) {
throw new GDataRequestException("Can not translate user query to search query",e,GDataResponse.BAD_REQUEST);
}
}
/**
* @return - the id of the requested feed
*/
public String getFeedId() {
return this.feedId;
}
/**
* @return - the entry id of the requested Entry if specified, otherwise
* <code>null</code>
*/
public String getEntryId() {
return this.entryId;
}
/**
* @return the version Id of the requested Entry if specified, otherwise
* <code>null</code>
*/
public String getEntryVersion() {
return this.entryVersion;
}
/**
* A Reader instance to read form the client input stream
*
* @return - the HttpServletRequest {@link Reader}
* @throws IOException -
* if an I/O Exception occurs
*/
public Reader getReader() throws IOException {
return this.request.getReader();
}
/**
* Returns the {@link HttpServletRequest} parameter map containing all
* <i>GET</i> request parameters.
*
* @return the parameter map
*/
@SuppressWarnings("unchecked")
public Map<String, String[]> getQueryParameter() {
return this.request.getParameterMap();
}
/**
* The {@link HttpServletRequest} request parameter names
*
* @return parameter names enumeration
*/
@SuppressWarnings("unchecked")
public Enumeration<String> getQueryParameterNames() {
return this.request.getParameterNames();
}
/**
* Either <i>Atom</i> or <i>RSS</i>
*
* @return - The output format requested by the client
*/
public OutputFormat getRequestedResponseFormat() {
return this.responseFormat;
}
private void generateIdentificationProperties()
throws GDataRequestException {
/* generate all needed data to identify the requested feed/entry */
String pathInfo = this.request.getPathInfo();
if (pathInfo.length() <= 1)
throw new GDataRequestException(
"No feed or entry specified for this request",GDataResponse.BAD_REQUEST);
StringTokenizer tokenizer = new StringTokenizer(pathInfo, "/");
this.service = tokenizer.nextToken();
if (!tokenizer.hasMoreTokens())
throw new GDataRequestException(
"Can not find feed id in requested path " + pathInfo,GDataResponse.BAD_REQUEST);
this.feedId = tokenizer.nextToken();
String appendix = tokenizer.hasMoreTokens() ? tokenizer.nextToken()
: null;
if (appendix == null){
this.isFeedRequest = true;
return;
}
if (appendix.equals(CATEGORY_QUERY_INDICATOR)) {
StringBuilder builder = new StringBuilder();
while (tokenizer.hasMoreTokens())
builder.append("/").append(tokenizer.nextToken());
this.categoryQuery = builder.toString();
} else {
this.entryId = appendix;
this.entryVersion = tokenizer.hasMoreTokens() ? tokenizer
.nextToken() : "";
}
this.isFeedRequest = (this.type == GDataRequestType.GET && (this.entryId == null
|| this.entryId.length() == 0 || (this.entryId.equals('/'))));
}
private void setOutputFormat() {
String formatParameter = this.request
.getParameter(RESPONSE_FORMAT_PARAMETER);
if (formatParameter == null)
return;
if (formatParameter.equalsIgnoreCase(RESPONSE_FORMAT_PARAMETER_RSS))
this.responseFormat = OutputFormat.RSS;
if (formatParameter.equalsIgnoreCase(RESPONSE_FORMAT_PARAMETER_HTML))
this.responseFormat = OutputFormat.HTML;
}
/**
* @return - the number of returned items per page
*/
public int getItemsPerPage() {
if (this.request.getParameter(ITEMS_PER_PAGE_PARAMETER) == null)
return DEFAULT_ITEMS_PER_PAGE;
int retval = -1;
try {
retval = new Integer(this.request
.getParameter(ITEMS_PER_PAGE_PARAMETER)).intValue();
} catch (Exception e) {
LOG.warn("Items per page could not be parsed - " + e.getMessage(),
e);
}
return retval < 0 ? DEFAULT_ITEMS_PER_PAGE : retval;
}
/**
* Start index represents the number of the first entry of the query -
* result. The order depends on the query. Is the query a search query the
* this value will be assigned to the score in a common feed query the value
* will be assigned to the update time of the entries.
*
* @return - the requested start index
*/
public int getStartIndex() {
String startIndex = this.request.getParameter(START_INDEX_NEXT_PAGE_PARAMETER);
if (startIndex == null)
return DEFAULT_START_INDEX;
int retval = -1;
try {
retval = new Integer(startIndex).intValue();
} catch (Exception e) {
LOG.warn("Start-index could not be parsed - not an integer - " + e.getMessage());
}
return retval < 0 ? DEFAULT_START_INDEX : retval;
}
/**
* The self id is the feeds <i>href</i> pointing to the requested resource
*
* @return - the self id
*/
public String getSelfId() {
StringBuilder builder = new StringBuilder();
builder.append(buildRequestIDString(false));
builder.append("?");
builder.append(getQueryString());
return builder.toString();
}
/**
* The previous id is the feeds <i>href</i> pointing to the previous result of the requested resource
*
* @return - the self id
*/
public String getPreviousId(){
int startIndex = getStartIndex();
if(startIndex == DEFAULT_START_INDEX )
return null;
StringBuilder builder = new StringBuilder();
builder.append(buildRequestIDString(false));
startIndex = startIndex-getItemsPerPage();
builder.append(getPreparedQueryString(startIndex<1?DEFAULT_START_INDEX:startIndex));
return builder.toString();
}
private String getPreparedQueryString(int startIndex){
String queryString = this.request.getQueryString();
String startIndexValue = this.request.getParameter(START_INDEX_NEXT_PAGE_PARAMETER);
String maxResultsValue = this.request.getParameter(ITEMS_PER_PAGE_PARAMETER);
StringBuilder builder = new StringBuilder("?");
if(maxResultsValue == null){
builder.append(ITEMS_PER_PAGE_PARAMETER).append("=").append(DEFAULT_ITEMS_PER_PAGE);
builder.append("&");
}
if(startIndexValue== null){
builder.append(START_INDEX_NEXT_PAGE_PARAMETER).append("=");
builder.append(Integer.toString(startIndex));
if(queryString!=null){
builder.append("&");
builder.append(queryString);
}
}else{
builder.append(queryString.replaceAll("start-index=[\\d]*",START_INDEX_NEXT_PAGE_PARAMETER+"="+Integer.toString(startIndex)));
}
return builder.toString();
}
/**
* The <i>href</i> id of the next page of the requested resource.
*
* @return the id of the next page
*/
public String getNextId() {
int startIndex = getStartIndex();
StringBuilder builder = new StringBuilder();
builder.append(buildRequestIDString(false));
startIndex = startIndex+getItemsPerPage();
builder.append(getPreparedQueryString(startIndex));
return builder.toString();
}
private String buildRequestIDString(boolean endingSlash) {
StringBuilder builder = new StringBuilder("http://");
builder.append(this.request.getHeader("Host"));
builder.append(this.request.getRequestURI());
if (!endingSlash && builder.charAt(builder.length() - 1) == '/')
builder.setLength(builder.length() - 1);
if (endingSlash && builder.charAt(builder.length() - 1) != '/')
builder.append("/");
return builder.toString();
}
/**
* This will return the current query string including all parameters.
* Additionally the <code>max-resul</code> parameter will be added if not
* specified.
* <p>
* <code>max-resul</code> indicates the number of results returned to the
* client. The default value is 25.
* </p>
*
* @return - the query string including all parameters
*/
public String getQueryString() {
String retVal = this.request.getQueryString();
if (this.request.getParameter(ITEMS_PER_PAGE_PARAMETER) != null)
return retVal;
String tempString = (retVal == null ? ITEMS_PER_PAGE_PARAMETER + "="
+ DEFAULT_ITEMS_PER_PAGE : "&" + ITEMS_PER_PAGE_PARAMETER + "="
+ DEFAULT_ITEMS_PER_PAGE);
return retVal == null ? tempString : retVal + tempString;
}
/**
* This enum represents the OutputFormat of the GDATA Server
*
* @author Simon Willnauer
*
*/
public static enum OutputFormat {
/**
* Output format ATOM. ATOM is the default response format.
*/
ATOM,
/**
* Output format RSS
*/
RSS,
/**
* Output format html if user defined xsl style sheet is present
*/
HTML
}
/**
* Returns the requested path including the domain name and the requested
* resource <i>http://www.apache.org/path/resource/</i>
*
* @return the context path
*/
public String getContextPath() {
if (this.contextPath == null)
this.contextPath = buildRequestIDString(true);
return this.contextPath;
}
/**
* Indicates the request type
*
* @author Simon Willnauer
*
*/
public enum GDataRequestType {
/**
* Type FeedRequest
*/
GET,
/**
* Type UpdateRequest
*/
UPDATE,
/**
* Type DeleteRequest
*/
DELETE,
/**
* Type InsertRequest
*/
INSERT
}
/**
* {@link GDataRequestType}
*
* @return the current request type
*/
public GDataRequestType getType() {
return this.type;
}
/**
* If the request is a {@link GDataRequestType#GET} request and there is no
* entry id specified, the requested resource is a feed.
*
* @return - <code>true</code> if an only if the requested resource is a
* feed
*/
public boolean isFeedRequested() {
return this.isFeedRequest ;
}
/**
* * If the request is a {@link GDataRequestType#GET} request and there is
* an entry id specified, the requested resource is an entry.
*
* @return - <code>true</code> if an only if the requested resource is an
* entry
*/
public boolean isEntryRequested() {
return !this.isFeedRequested();
}
/**
* @return - <code>true</code> if an only if the user request is a search request, otherwise <code>false</code>
*/
public boolean isSearchRequested(){
return this.isSearchRequest;
}
/**
* @return the configuration for this request
*/
public ProvidedService getConfigurator() {
return this.configurator;
}
/**
* @return - Returns the Internet Protocol (IP) address of the client or
* last proxy that sent the request.
*/
public String getRemoteAddress() {
return this.request.getRemoteAddr();
}
/**
* @return - the value for the send auth token. The auth token will be send
* as a request <tt>Authentication</tt> header.
*/
public String getAuthToken() {
String token = this.request.getHeader(HTTP_HEADER_AUTH);
if (token == null)
return null;
token = token.substring(token.indexOf("=") + 1);
return token;
}
/**
* @return - Returns an array containing all of the Cookie objects the
* client sent with underlying HttpServletRequest.
*/
public Cookie[] getCookies() {
return this.request.getCookies();
}
/**
* @return - the cookie set instead of the authentication token or
* <code>null</code> if no auth cookie is set
*/
public Cookie getAuthCookie() {
Cookie[] cookies = this.request.getCookies();
if (cookies == null)
return null;
for (int i = 0; i < cookies.length; i++) {
if (cookies[i].getName().equals(AuthenticationController.TOKEN_KEY))
return cookies[i];
}
return null;
}
/**
* @return - the date string of the <tt>If-Modified-Since</tt> HTTP
* request header, or null if header is not set
*/
public String getModifiedSince() {
return this.request.getHeader(HTTP_HEADER_IF_MODIFIED_SINCE);
}
/**
* @return - the underlying HttpServletRequest
*/
public HttpServletRequest getHttpServletRequest() {
return this.request;
}
protected String getTranslatedQuery(){
return this.translatedSearchQuery;
}
}
|