/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.xmlui.opensearch;
import java.io.IOException;
import java.io.Serializable;
import java.net.URLDecoder;
import java.sql.SQLException;
import java.util.Map;
import org.apache.avalon.excalibur.pool.Recyclable;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.cocoon.ProcessingException;
import org.apache.cocoon.caching.CacheableProcessingComponent;
import org.apache.cocoon.environment.ObjectModelHelper;
import org.apache.cocoon.environment.Request;
import org.apache.cocoon.environment.SourceResolver;
import org.apache.cocoon.generation.AbstractGenerator;
import org.apache.cocoon.util.HashUtil;
import org.apache.excalibur.source.SourceValidity;
import org.apache.excalibur.source.impl.validity.ExpiresValidity;
import org.dspace.app.util.OpenSearch;
import org.dspace.app.xmlui.utils.ContextUtil;
import org.dspace.content.DSpaceObject;
import org.dspace.core.ConfigurationManager;
import org.dspace.core.Context;
import org.dspace.sort.SortException;
import org.dspace.sort.SortOption;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
/**
* This class provides the common attributes and methods for parameter parsing,
* cache key and validity generations.
*
* Parameters are parsed, sanitized and default values are assigned to them.
* Subclasses requiring a custom parameter parsing is able to overwrite this setup() method,
* but in most cases, this method should be enough.
*
* @author Richard Rodgers
* @author Nestor Oviedo
*/
public abstract class AbstractOpenSearchGenerator extends AbstractGenerator
implements CacheableProcessingComponent, Recyclable
{
/** Cache of this object's validity */
private ExpiresValidity validity = null;
/** The results requested format */
protected String format = null;
/** the search query string */
protected String query = null;
/** optional search scope (= handle of container) or null */
protected DSpaceObject scope = null;
/** optional sort specification */
protected SortOption sort = null;
/** sort order, see SortOption **/
protected String sortOrder = null;
/** results per page */
protected int rpp = 0;
/** first result index is 1 because OpenSearch starts counting at 1 */
protected int start = 1;
/** the results document (cached) */
protected Document resultsDoc = null;
/** default value for results per page parameter */
public static int DEFAULT_RPP = 20;
/** max allowed value for results per page parameter */
public static int MAX_RPP = 100;
/**
* Generate the unique caching key.
* This key includes the concrete class name to ensure uniqueness
*/
public Serializable getKey()
{
StringBuffer key = new StringBuffer("key:");
// Include the concrete class as part of the cache key
key.append(this.getClass().getName());
if (scope != null)
{
key.append(scope.getHandle());
}
key.append(query);
if (format != null)
{
key.append(format);
}
if (sort != null)
{
key.append(sort.getNumber());
}
key.append(start);
key.append(rpp);
key.append(sortOrder);
return HashUtil.hash(key.toString());
}
/**
* Generate the cache validity object, based on the websvc.opensearch.validity config property
*/
public SourceValidity getValidity()
{
if (this.validity == null)
{
long expiry = ConfigurationManager
.getLongProperty("websvc.opensearch.validity") * 60 * 60 * 1000;
this.validity = new ExpiresValidity(expiry);
}
return this.validity;
}
/**
* Setup configuration for this request, parameter parsing and sanitization.
* This methods should be overwrite only if the subclass requires a different parameter parsing
*/
public void setup(SourceResolver resolver, Map objectModel, String src,
Parameters par) throws ProcessingException, SAXException, IOException
{
super.setup(resolver, objectModel, src, par);
Context context = null;
try
{
context = ContextUtil.obtainContext(objectModel);
}
catch (SQLException e)
{
throw new ProcessingException("Couldn't get DSpace Context object", e);
}
Request request = ObjectModelHelper.getRequest(objectModel);
// Query param (defaults to empty query)
this.query = request.getParameter("query");
if (query == null)
{
query = "";
}
query = URLDecoder.decode(query, "UTF-8");
// Format param (defaults to atom)
this.format = request.getParameter("format");
if (format == null || format.length() == 0 || !OpenSearch.getFormats().contains(format))
{
format = "atom";
}
// Scope param (throws ProcessingException when scope is not a valid community or collection)
String scopeParam = request.getParameter("scope");
try
{
scope = OpenSearch.resolveScope(context, scopeParam);
}
catch (SQLException e)
{
throw new ProcessingException("Error resolving scope handle param "+scopeParam, e);
}
// Sort field param (defaults to 0)
String srt = request.getParameter("sort_by");
int sortValue = -1;
try
{
if (srt != null && srt.length() > 0)
sortValue = Integer.valueOf(srt);
}
catch (NumberFormatException e)
{
// do nothing. preserves the default value
}
try
{
this.sort = SortOption.getSortOption(sortValue);
}
catch (SortException e)
{
// This exception is thrown when there is a configuration error. We wrap it in a ProcessingException
// in order to be able to rethrow it
throw new ProcessingException("Error obtaining SortOptions", e);
}
// Sort order param if the sort param is not null (defaults to asc)
if(this.sort != null)
{
String order = request.getParameter("order");
this.sortOrder = (order == null || order.length() == 0 || order.toLowerCase().startsWith("asc")) ?
SortOption.ASCENDING : SortOption.DESCENDING;
}
// Start index param (has to be >= 1)
String st = request.getParameter("start");
try
{
this.start = (st == null || st.length() == 0) ? 0 : Integer.valueOf(st);
if (this.start < 1)
this.start = 1;
}
catch (NumberFormatException e)
{
this.start = 1;
}
// Results per page param (defaults to DEFAULT_RPP)
String pp = request.getParameter("rpp");
try
{
this.rpp = (pp == null || pp.length() == 0) ? DEFAULT_RPP : Integer.valueOf(pp);
if(this.rpp <= 0 || this.rpp > MAX_RPP)
this.rpp = DEFAULT_RPP;
}
catch (NumberFormatException e)
{
this.rpp = DEFAULT_RPP;
}
}
/**
* Recycle
*/
public void recycle()
{
this.format = null;
this.query = null;
this.scope = null;
this.sort = null;
this.rpp = 0;
this.start = 1;
this.sortOrder = null;
this.resultsDoc = null;
this.validity = null;
super.recycle();
}
}