
com.day.cq.commons.servlets.AbstractListServlet Maven / Gradle / Ivy
/*
* Copyright 1997-2010 Day Management AG
* Barfuesserplatz 6, 4001 Basel, Switzerland
* All Rights Reserved.
*
* This software is the confidential and proprietary information of
* Day Management AG, ("Confidential Information"). You shall not
* disclose such Confidential Information and shall use it only in
* accordance with the terms of the license agreement you entered into
* with Day.
*/
package com.day.cq.commons.servlets;
import java.io.IOException;
import java.io.StringWriter;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import javax.jcr.Session;
import javax.servlet.ServletException;
import org.apache.commons.collections.Predicate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.ReferencePolicy;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.commons.json.JSONArray;
import org.apache.sling.commons.json.JSONException;
import org.apache.sling.commons.json.JSONObject;
import org.apache.sling.commons.json.io.JSONWriter;
import org.apache.sling.jcr.api.SlingRepository;
import org.osgi.framework.ServiceReference;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.day.cq.commons.ListInfoProvider;
import com.day.cq.commons.TidyJSONWriter;
/**
* The AbstractListServlet
provides base functionality such as
* sorting and paging for servlets that feed Ext grids (like in the SiteAdmin)
* with JSON.
* Normally, the list of children of the addressed resource are returned.
* Alternatively, the paging index of an item can be requested.
*/
@Component(metatype = false, componentAbstract = true)
public class AbstractListServlet extends AbstractPredicateServlet {
/**
* Default logger
*/
private static final Logger log = LoggerFactory.getLogger(AbstractListServlet.class);
/**
* Collator instance
*/
private Collator collator = Collator.getInstance();
protected static final long serialVersionUID = 2138470595710406273L;
/**
* Parameter to use for tidy JSON. If present, indentation and line breaks are
* added for better legibility.
*/
public static final String TIDY = "tidy";
/**
* Parameter to specify the start index with when using paging.
* Typically used in conjunction with {@link #PAGE_LIMIT}.
* For the items of the page n
, use a start index of
* limit + (n - 1)
.
*/
public static final String PAGE_START = "start";
/**
* Parameter to specify the limit of items per page when using paging.
* Typically used in conjunction with {@link #PAGE_START}.
*/
public static final String PAGE_LIMIT = "limit";
/**
* Parameter to specify which property use for sorting. Defaults to "index".
*/
public static final String SORT_KEY = "sort";
/**
* Parameter to specify the direction to use for sorting. Defaults to
* {@link #SORT_ASCENDING}.
*/
public static final String SORT_DIR = "dir";
/**
* Value to use for {@link #SORT_DIR} to use ascending order (default).
*/
public static final String SORT_ASCENDING = "ASC";
/**
* Value to use for {@link #SORT_DIR} to use descending order.
*/
public static final String SORT_DESCENDING = "DESC";
/**
* Parameter to use in conjunction with {@link #PAGE_INDEX} to determine the
* paging index of an item. If both parameters are present, the paging
* index of the item with the specified path will be returned instead of the
* list of children.
*/
public static final String PATH = "path";
/**
* Parameter to use in conjunction with {@link #PATH} to determine the
* paging index of an item. If this parameter is present, the paging
* index of the item with the path specified in {@link #PATH} will be returned
* instead of the list of children.
*/
public static final String PAGE_INDEX = "index";
/**
* Parameter to use to specify the name(s) of custom properties that should be
* returned for each item in the list. If the properties exist on the item's
* resource, their values will be returned as additional JSON properties.
*/
public static final String PROP = "prop";
// /**
// * Parameter to use to specify the resource type to use to render a single
// * list item when doing HTML rendering.
// */
// public static final String ITEM_RESOURCE_TYPE = "itemResourceType";
//
protected static final String DEFAULT_TIDY = "true";
protected static final String DEFAULT_SORT_KEY = "index";
protected static final String DEFAULT_SORT_DIR = SORT_ASCENDING;
protected static final String CONTENT_TYPE = "application/json";
// protected static final String HTML_CONTENT_TYPE = "text/html";
//
protected static final String ENCODING = "utf-8";
// ----< services >---------------------------------------------------------
@Reference(
policy = ReferencePolicy.STATIC
)
@SuppressWarnings( { "UnusedDeclaration" })
protected SlingRepository repository;
@Reference(
cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE,
policy = ReferencePolicy.DYNAMIC
)
@SuppressWarnings( { "UnusedDeclaration" })
protected ListInfoProvider listInfoProvider;
// ----< members >----------------------------------------------------------
/**
* @deprecated The admin session is no longer available and will be always {@code null}. Extending components requiring specific
* permissions should get their own session using the {@link SlingRepository#loginService(String, String)}.
*/
@Deprecated
protected Session admin = null;
private final List delayedProviders = new ArrayList();
private final List providers = new ArrayList();
private List cachedProviders = Collections.emptyList();
private ComponentContext componentContext;
// ----< servlet methods >--------------------------------------------------
/**
* {@inheritDoc}
*/
@Override
protected void doGet(SlingHttpServletRequest request,
SlingHttpServletResponse response, Predicate predicate)
throws ServletException, IOException {
try {
// get list items
List items = getItems(request, predicate);
int total = items.size();
// boolean isHTML = "html".equals(request.getRequestPathInfo().getExtension());
// if (!isHTML) {
if (request.getParameter(PAGE_INDEX) != null) {
// return page index of item with given path
String path = request.getParameter(PATH);
writePagingIndex(request, response, items, path);
return;
}
// }
items = processItems(request, items, total);
// if (isHTML) {
// writeHtml(request, response, items);
// } else {
// get custom properties
String[] customProps = request.getParameterValues(PROP);
write(request, response, items, customProps, total);
// }
} catch (Exception e) {
throw new ServletException(e);
}
}
// ----< internal >---------------------------------------------------------
/**
* Returns the list items based on the specified request and predicate.
* The list items are typically generated from the children of the
* Resource
provided by the request. The optional predicate
* can be used for evaluation.
* @param request The request
* @param predicate The predicate (optional)
* @return The list items
* @throws Exception if unable to retrieve items
*/
@SuppressWarnings({"UnusedDeclaration"})
protected List getItems(SlingHttpServletRequest request,
Predicate predicate) throws Exception {
// implement in subclass
return null;
}
/**
* Processes the specified list items based on the request parameters.
* By default, sorting and paging is applied.
* @see #applySorting(SlingHttpServletRequest, List)
* @see #applyPaging(SlingHttpServletRequest, List, int)
* @param request The request
* @param items The list items
* @param total The total number of list items
* @return The processed list items
*/
protected List processItems(SlingHttpServletRequest request,
List items, int total) {
return applyPaging(request, applySorting(request, items), total);
}
/**
* Applies sorting to the list items if specified in the request.
* @param request The request
* @param items The list items
* @return The sorted list items
*/
protected List applySorting(SlingHttpServletRequest request,
List items) {
String sortKey = request.getParameter(SORT_KEY) != null ?
request.getParameter(SORT_KEY) : DEFAULT_SORT_KEY;
String sortDir = request.getParameter(SORT_DIR) != null ?
request.getParameter(SORT_DIR) : DEFAULT_SORT_DIR;
/* set collator strength */
collator.setStrength(Collator.PRIMARY);
// apply sorting
if (!(DEFAULT_SORT_KEY.equals(sortKey) && DEFAULT_SORT_DIR.equals(sortDir))) {
Collections.sort(items, new ListItemComparator(sortKey));
if (SORT_DESCENDING.equals(sortDir)) {
Collections.reverse(items);
}
}
return items;
}
/**
* Applies paging to the list items if specified in the request.
* @param request The request
* @param items The list items
* @param total The total number of list items
* @return The truncated list items
*/
protected List applyPaging(SlingHttpServletRequest request,
List items, int total) {
int start = 0;
int end = Integer.MAX_VALUE;
if (request.getParameter(PAGE_START) != null) {
try {
start = Integer.parseInt(request.getParameter(PAGE_START));
} catch (Exception ignored) {
}
}
if (request.getParameter(PAGE_LIMIT) != null) {
try {
end = Integer.parseInt(request.getParameter(PAGE_LIMIT)) + start;
} catch (Exception ignored) {
}
}
// truncate list
if (start > 0 || total > end - start) {
if (start > total - 1) {
start = total;
}
if (end > total - 1) {
end = total;
}
items = items.subList(start, end);
}
return items;
}
/**
* Returns the paging index of the item with the specified path or
* -1
if item not part of the specified items.
* @param request The request
* @param items The list items
* @param path The path of the item
* @return The paging index
*/
protected long getPagingIndex(SlingHttpServletRequest request, List items, String path) {
if (path == null) {
return -1;
}
int limit = Integer.MAX_VALUE;
if (request.getParameter(PAGE_LIMIT) != null) {
try {
limit = Integer.parseInt(request.getParameter(PAGE_LIMIT));
} catch (Exception ignored) {
}
}
Iterator iterator = items.iterator();
long index = 0;
int counter = 0;
boolean found = false;
while (iterator.hasNext()) {
if (path.equals(((ListItem)iterator.next()).getResource().getPath())) {
found = true;
break;
}
if (++counter == limit) {
index++;
counter = 0;
}
}
return found ? index : -1;
}
/**
* Writes the list to a JSON writer.
* @param request The request
* @param response The response
* @param listItems The list items
* @param customProps The names of the custom properties to include
* @param total The total number of list items
* @throws Exception if unable to write the list
*/
protected void write(SlingHttpServletRequest request,
SlingHttpServletResponse response,
List listItems,
String[] customProps, int total)
throws Exception {
response.setContentType(CONTENT_TYPE);
response.setCharacterEncoding(ENCODING);
// Final result
JSONObject json = new JSONObject();
// Get list of custom providers
List listInfoProviders = cachedProviders;
// Items list
JSONArray listArray = new JSONArray();
for (ListItem item : listItems) {
// Base list item information
StringWriter out = new StringWriter();
JSONWriter writer = new TidyJSONWriter(out);
writer.setTidy(DEFAULT_TIDY.equals(request.getParameter(TIDY)));
item.write(writer, customProps);
// Reparse object
JSONObject info = new JSONObject(out.toString());
// Additional item information from configured list info providers
Resource resource = item.getResource();
for (ListInfoProvider p : listInfoProviders) {
long t0 = System.currentTimeMillis();
p.updateListItemInfo(request, info, resource);
long t1 = System.currentTimeMillis();
log.debug("{}.updateListItemInfo() in {}ms", p.getClass().getName(), t1 - t0);
}
// Append object to array
listArray.put(info);
}
json.put("pages", listArray);
// Global list information
json.put("results", total);
// Add global information from list info providers
Resource resource = request.getResource();
for (ListInfoProvider p : listInfoProviders) {
long t0 = System.currentTimeMillis();
p.updateListGlobalInfo(request, json, resource);
long t1 = System.currentTimeMillis();
log.debug("{}.updateListGlobalInfo() in {}ms", p.getClass().getName(), t1 - t0);
}
// Write JSON response
json.write(response.getWriter());
}
// /**
// * Writes the list as HTML.
// * @param request The request
// * @param response The response
// * @param listItems The list items
// * @throws Exception if unable to write the list
// */
// protected void writeHtml(SlingHttpServletRequest request,
// SlingHttpServletResponse response,
// List listItems)
// throws Exception {
// response.setContentType(HTML_CONTENT_TYPE);
// response.setCharacterEncoding(ENCODING);
//
// // Get item resource type
// String itemResourceType = request.getParameter(ITEM_RESOURCE_TYPE);
// if (itemResourceType == null || request.getResourceResolver().getResource(itemResourceType) == null) {
// log.error("No resource type to render list items");
// return;
// }
//
// // Items list
// RequestDispatcherOptions requestDispatcherOptions = new RequestDispatcherOptions(null);
// requestDispatcherOptions.setForceResourceType(itemResourceType);
// requestDispatcherOptions.setReplaceSelectors("");
//
// for (ListItem item : listItems) {
// RequestDispatcher dispatcher = request.getRequestDispatcher(item.getResource().getPath() + ".html", requestDispatcherOptions);
// dispatcher.include(request, response);
// }
// }
//
/**
* Writes a key and its value to the specified JSON writer.
* @param out The JSON
* @param key The name of the key
* @param value The value of the key
* @throws JSONException if unable to write the key
*/
protected void writeKey(JSONWriter out, String key, Object value)
throws JSONException {
out.key(key).value(value);
}
/**
* Writes a key and its value to the specified JSON writer. If the
* value is null
, the key is omitted.
* @param out The JSON
* @param key The name of the key
* @param value The value of the key
* @throws JSONException if unable to write the key
*/
@SuppressWarnings({"UnusedDeclaration"})
protected void writeOptionalKey(JSONWriter out, String key, Object value)
throws JSONException {
if (value != null) {
writeKey(out, key, value);
}
}
/**
* Writes a key and its date value to the specified JSON writer. If the
* value is null
, the key is omitted.
* @param out The JSON
* @param key The name of the key
* @param value The date value of the key
* @throws JSONException if unable to write the key
*/
@SuppressWarnings({"UnusedDeclaration"})
protected void writeOptionalDateKey(JSONWriter out, String key, Calendar value)
throws JSONException {
if (value != null) {
out.key(key).value(value.getTimeInMillis());
}
}
/**
* Writes the specified properties of the resource to the JSON writer.
* If the value is null
, the key is omitted.
* @param out The JSON
* @param resource The resource
* @param customProps The properties
* @throws JSONException if unable to write the properties
*/
@SuppressWarnings({"UnusedDeclaration"})
protected void writeCustomProperties(JSONWriter out, Resource resource,
String[] customProps) throws JSONException {
if (customProps != null) {
ValueMap props = resource.adaptTo(ValueMap.class);
if (props != null) {
for (String name : customProps) {
writeOptionalKey(out, name, props.get(name, null));
}
}
}
}
/**
* Writes the paging index of the item with the specified path to a JSON writer.
* @param request The request
* @param response The response
* @param listItems The list items
* @param path The path of the item
* @throws Exception if unable to write the index
*/
protected void writePagingIndex(SlingHttpServletRequest request,
SlingHttpServletResponse response,
List listItems, String path) throws Exception {
response.setContentType(CONTENT_TYPE);
response.setCharacterEncoding(ENCODING);
JSONWriter writer = new JSONWriter(response.getWriter());
writer.setTidy(DEFAULT_TIDY.equals(request.getParameter(TIDY)));
writer.object();
writer.key(PAGE_INDEX).value(getPagingIndex(request, applySorting(request, listItems), path));
writer.endObject();
}
// ----< SCR Integration >--------------------------------------------------
@SuppressWarnings({"UnusedDeclaration"})
protected void activate(ComponentContext context) throws Exception {
synchronized (delayedProviders) {
componentContext = context;
for (ServiceReference ref : delayedProviders) {
registerProvider(ref);
}
delayedProviders.clear();
}
}
@SuppressWarnings({"UnusedDeclaration"})
protected void deactivate(ComponentContext context) {
componentContext = null;
}
/**
* The ListItem
interface defines a sortable item of the list.
* Sortable fields must be public for comparison.
* @see ListItemComparator
*/
@SuppressWarnings({"UnusedDeclaration"})
public interface ListItem {
public static final String INDEX = "index";
public static final String PATH = "path";
public static final String LABEL = "label";
public static final String TYPE = "type";
public static final String TITLE = "title";
public static final String DESCRIPTION = "description";
public static final String LAST_MODIFIED = "lastModified";
public static final String LAST_MODIFIED_BY = "lastModifiedBy";
public static final String LOCKED_BY = "lockedBy";
public static final String MONTHLY_HITS = "monthlyHits";
public static final String REPLICATION = "replication";
public static final String REPLICATION_NUM_QUEUED = "numQueued";
public static final String REPLICATION_ACTION = "action";
public static final String REPLICATION_PUBLISHED = "published";
public static final String REPLICATION_PUBLISHED_BY = "publishedBy";
public static final String IN_WORKFLOW = "inWorkflow";
public static final String WORKFLOWS = "workflows";
public static final String WORKFLOW_MODEL = "model";
public static final String WORKFLOW_STARTED = "started";
public static final String WORKFLOW_STARTED_BY = "startedBy";
public static final String WORKFLOW_SUSPENDED = "suspended";
public static final String WORKFLOW_WORK_ITEMS = "workItems";
public static final String WORKFLOW_WORK_ITEM_TITLE = "item";
public static final String WORKFLOW_WORK_ITEM_ASSIGNEE = "assignee";
public static final String SCHEDULED_TASKS = "scheduledTasks";
public static final String SCHEDULED_TASK_VERSION = "version";
public static final String SCHEDULED_TASK_SCHEDULED = "scheduled";
public static final String SCHEDULED_TASK_SCHEDULED_BY = "scheduledBy";
public static final String SCHEDULED_TASK_TYPE = "type";
public static final String SCHEDULED_ACTIVATION_WORKFLOW_ID =
"/etc/workflow/models/scheduled_activation/jcr:content/model";
public static final String SCHEDULED_DEACTIVATION_WORKFLOW_ID =
"/etc/workflow/models/scheduled_deactivation/jcr:content/model";
/**
* Writes the list item to the specified JSON writer.
* @param out The writer
* @param customProps The names of the custom properties to include
* @throws Exception unable to write list item
*/
public void write(JSONWriter out, String[] customProps) throws Exception;
/**
* Get item resource
* @return Item resource
*/
public Resource getResource();
}
/**
* The ListItemComparator
compares public fields of
* {@link ListItem}s.
*/
public class ListItemComparator implements Comparator {
private String compareField;
/**
* Creates a new ListItemComparator
instance.
* @param compareField The public field to compare
*/
public ListItemComparator(String compareField) {
this.compareField = compareField;
}
/**
* {@inheritDoc}
*/
public int compare(ListItem o1, ListItem o2) {
Object v1, v2;
try {
v1 = o1.getClass().getField(compareField).get(o1);
v2 = o2.getClass().getField(compareField).get(o2);
if (v1 instanceof String && v2 instanceof String) {
return (collator != null) ? collator.compare((String)v1, (String)v2) : ((String)v1).compareTo((String)v2);
} else if (v1 instanceof Integer && v2 instanceof Integer) {
int int1 = (Integer)v1;
int int2 = (Integer)v2;
return (int1 > int2 ? 1 : int1 != int2 ? -1 : 0);
} else if (v1 instanceof Long && v2 instanceof Long) {
long long1 = (Long)v1;
long long2 = (Long)v2;
return (long1 > long2 ? 1 : long1 != long2 ? -1 : 0);
}
} catch (Exception ignored) {
}
return 0;
}
}
protected void bindListInfoProvider(ServiceReference ref) {
synchronized (delayedProviders) {
if (componentContext == null) {
delayedProviders.add(ref);
} else {
registerProvider(ref);
}
}
}
protected void unbindListInfoProvider(ServiceReference ref) {
synchronized (delayedProviders) {
this.delayedProviders.remove(ref);
this.providers.remove(ref);
}
}
protected void registerProvider(ServiceReference ref) {
providers.add(ref);
List pvs = new ArrayList();
for (ServiceReference current : providers) {
ListInfoProvider provider = (ListInfoProvider) componentContext.locateService("listInfoProvider", current);
if (provider != null) {
pvs.add(provider);
}
}
cachedProviders = pvs;
}
protected Collator getCollator() {
return collator;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy