/*
* � Copyright IBM Corp. 2011
*
* 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 com.ibm.domino.das.resources;
import static com.ibm.domino.commons.model.IGatekeeperProvider.FEATURE_REST_API_DATA_VIEW_ENTRIES;
import static com.ibm.domino.das.service.DataService.STAT_VIEW_ENTRIES;
import static com.ibm.domino.das.servlet.DasServlet.DAS_LOGGER;
import static com.ibm.domino.services.HttpServiceConstants.HEADER_CONTENT_RANGE;
import static com.ibm.domino.services.HttpServiceConstants.HEADER_RANGE;
import static com.ibm.domino.services.HttpServiceConstants.HEADER_RANGE_ITEMS;
import static com.ibm.domino.services.rest.RestParameterConstants.*;
import static com.ibm.domino.services.rest.RestServiceConstants.ITEM_FORM;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.StringReader;
import java.net.URI;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.StreamingOutput;
import javax.ws.rs.core.UriInfo;
import lotus.domino.Database;
import lotus.domino.Document;
import lotus.domino.NotesException;
import lotus.domino.View;
import com.ibm.commons.util.StringUtil;
import com.ibm.commons.util.io.json.JsonJavaFactory;
import com.ibm.commons.util.io.json.JsonJavaObject;
import com.ibm.commons.util.io.json.JsonParser;
import com.ibm.domino.commons.util.UriHelper;
import com.ibm.domino.das.service.DataService;
import com.ibm.domino.das.utils.ErrorHelper;
import com.ibm.domino.services.Loggers;
import com.ibm.domino.services.ServiceException;
import com.ibm.domino.services.content.JsonViewEntryCollectionContent;
import com.ibm.domino.services.rest.das.RestDocumentNavigator;
import com.ibm.domino.services.rest.das.RestDocumentNavigatorFactory;
import com.ibm.domino.services.rest.das.view.RestViewNavigator;
import com.ibm.domino.services.rest.das.view.RestViewNavigatorFactory;
import com.ibm.domino.services.rest.das.view.ViewParameters;
import com.ibm.domino.services.rest.das.view.impl.DefaultViewParameters;
import com.ibm.domino.services.util.JsonWriter;
@Path(PARAM_DATA + PARAM_SEPERATOR + PARAM_COLLECTIONS + "/{key}/{value}") // $NON-NLS-1$
public class ViewEntryCollectionResource extends ViewBaseResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response getViewEntries(@Context final UriInfo uriInfo,
@PathParam("key") final String keyName, // $NON-NLS-1$
@PathParam("value") final String keyValue, // $NON-NLS-1$
@HeaderParam(HEADER_RANGE) final String range,
@QueryParam(PARAM_COMPACT) final boolean compact,
@QueryParam(PARAM_VIEW_START) final String start,
@QueryParam(PARAM_VIEW_COUNT) final String count,
@QueryParam(PARAM_VIEW_STARTINDEX) final String startIndex,
@QueryParam(PARAM_VIEW_PAGESIZE) final String pageSize,
@QueryParam(PARAM_VIEW_PAGEINDEX) final String pageIndex,
@QueryParam(PARAM_VIEW_SEARCH) final String search,
@QueryParam(PARAM_VIEW_SEARCHMAXDOCS) final String searchMaxDocs,
@QueryParam(PARAM_VIEW_SORTCOLUMN) final String sortColumn,
@QueryParam(PARAM_VIEW_SORTORDER) final String sortOrder,
@QueryParam(PARAM_VIEW_STARTKEYS) final String startKeys,
@QueryParam(PARAM_VIEW_SYSTEMCOLUMNS) final String systemColumns,
@QueryParam(PARAM_VIEW_KEYS) final String keys,
@QueryParam(PARAM_VIEW_KEYSEXACTMATCH) final String keysExactMatch,
@QueryParam(PARAM_VIEW_EXPANDLEVEL) final String expandLevel,
@QueryParam(PARAM_VIEW_CATEGORY) final String categoryFilter,
@QueryParam(PARAM_VIEW_PARENTID) final String parentId,
@QueryParam(PARAM_VIEW_ENTRYCOUNT) final String entryCount) {
DAS_LOGGER.traceEntry(this, "getViewEntries"); // $NON-NLS-1$
DataService.beforeRequest(FEATURE_REST_API_DATA_VIEW_ENTRIES, STAT_VIEW_ENTRIES);
final Database database = this.getDatabase(DB_ACCESS_VIEWS);
if ( database == null ) {
// This resource should always have a database context
throw new WebApplicationException(ErrorHelper.createErrorResponse("URI path must include a database.", Response.Status.NOT_FOUND)); // $NLX-ViewEntryCollectionResource.URIpathmustincludeadatabase-1$
}
final ResponseBuilder builder = Response.ok();
abstract class StreamingOutputImpl implements StreamingOutput {
Response response = null;
public void setResponse(Response response) {
this.response = response;
}
}
StreamingOutputImpl streamJsonEntity = new StreamingOutputImpl() {
//@Override
public void write(OutputStream outputStream) throws IOException {
View view = null;
int iStart = 0;
int iCount = DEFAULT_VIEW_COUNT;
try {
view = getCurrentView(keyName, keyValue, database);
view.setAutoUpdate(false);
// Handle parameters.
DefaultViewParameters parameters = new DefaultViewParameters();
int iGlobalValues = DefaultViewParameters.GLOBAL_ALL;
int iSystemColumns = DefaultViewParameters.SYSCOL_ALL;
parameters.setDefaultColumns(true);
URI baseUri= UriHelper.copy(uriInfo.getAbsolutePath(),DataService.isUseRelativeUrls());
baseUri = UriHelper.appendPathSegment(baseUri, PARAM_UNID);
URI baseDocumentUri= UriHelper.copy(uriInfo.getAbsolutePath(),DataService.isUseRelativeUrls());
baseDocumentUri = UriHelper.trimAtLast(baseDocumentUri, PARAM_SEPERATOR + PARAM_COLLECTIONS);
baseDocumentUri = UriHelper.appendPathSegment(baseDocumentUri, PARAM_DOCUMENTS);
baseDocumentUri = UriHelper.appendPathSegment(baseDocumentUri, PARAM_UNID);
// Header submitted by the client:
// Range: items=0-24
if(StringUtil.isNotEmpty(range) && range.startsWith(HEADER_RANGE_ITEMS)) {
int pos = HEADER_RANGE_ITEMS.length();
int sep = range.indexOf('-',pos);
iStart = Integer.valueOf(range.substring(pos,sep));
int last = Integer.valueOf(range.substring(sep+1));
iCount = last-iStart+1;
}
else {
// If the header does not contain range information, we still look at the url.
// Currently the plan is to support both iStart and iCount and page, ps and si.
if (StringUtil.isNotEmpty(start)) {
iStart = getParameterInt(PARAM_VIEW_START, start);
}
if (StringUtil.isNotEmpty(count)) {
iCount = getParameterInt(PARAM_VIEW_COUNT, count);
}
// The following three parameters page, ps and si map to iStart and iCount.
// iCount = ps
// iStart = ps * page + si
if (StringUtil.isNotEmpty(pageSize)) {
iCount = getParameterInt(PARAM_VIEW_PAGESIZE, pageSize);
}
if (StringUtil.isNotEmpty(pageIndex)) {
int page = getParameterInt(PARAM_VIEW_PAGEINDEX, pageIndex);
iStart = page * iCount;
}
if (StringUtil.isNotEmpty(startIndex)) {
try {
int si = getParameterInt(PARAM_VIEW_STARTINDEX, startIndex);
iStart += si;
} catch (NumberFormatException nfe) {}
}
}
// SPR# KHRL9NZTRZ: Limit the page size
int maxCount = DataService.getMaxViewEntries();
if ( iCount > maxCount ) {
String msg = StringUtil.format("Limit exceeded. Cannot read more than {0} entries.", maxCount); // $NLX-ViewEntryCollectionResource.LimitexceededCannotreadmorethan0e-1$
throw new WebApplicationException(
ErrorHelper.createErrorResponse(msg, Response.Status.BAD_REQUEST));
}
// We passed all the checks. Set the start index and count
parameters.setStart(iStart);
parameters.setCount(iCount);
if (StringUtil.isNotEmpty(search)) {
parameters.setFtSearch(search);
}
if (StringUtil.isNotEmpty(searchMaxDocs)) {
parameters.setFtMaxDocs(getParameterInt(PARAM_VIEW_SEARCHMAXDOCS, searchMaxDocs));
}
if (StringUtil.isNotEmpty(sortColumn)) {
parameters.setSortColumn(sortColumn);
}
if (StringUtil.isNotEmpty(sortOrder)) {
parameters.setSortOrder(sortOrder);
}
if (StringUtil.isNotEmpty(startKeys)) {
parameters.setStartKey(startKeys);
}
if (StringUtil.isNotEmpty(keys)) {
parameters.setKeys(keys);
}
if (StringUtil.isNotEmpty(keysExactMatch)) {
parameters.setKeysExactMatch(keysExactMatch.compareToIgnoreCase(PARAM_VALUE_TRUE) == 0);
}
if (StringUtil.isNotEmpty(expandLevel)) {
parameters.setMaxLevel(getParameterInt(PARAM_VIEW_EXPANDLEVEL, expandLevel));
} else {
parameters.setMaxLevel(Integer.MAX_VALUE);
}
if (StringUtil.isNotEmpty(categoryFilter)) {
parameters.setCategoryFilter(categoryFilter);
}
if (StringUtil.isNotEmpty(parentId)) {
parameters.setParentId(parentId);
}
if (StringUtil.isNotEmpty(entryCount) && entryCount.compareToIgnoreCase(PARAM_VALUE_FALSE) == 0) {
iGlobalValues &= ~ViewParameters.GLOBAL_TOPLEVEL;
}
if (StringUtil.isNotEmpty(systemColumns)) {
iSystemColumns = getParameterInt(PARAM_VIEW_SYSTEMCOLUMNS, systemColumns, true);
}
parameters.setGlobalValues(iGlobalValues);
parameters.setSystemColumns(iSystemColumns);
OutputStreamWriter streamWriter = new OutputStreamWriter(outputStream);
JsonWriter jsonWriter = new JsonWriter(streamWriter, compact);
JsonViewEntryCollectionContent content = new JsonViewEntryCollectionContent(view, baseUri.toString(), baseDocumentUri.toString());
String header = content.getContentRangeHeader(parameters);
if (header != null) {
response.getMetadata().add(HEADER_CONTENT_RANGE, header);
}
content.writeViewEntryCollection(jsonWriter, parameters);
streamWriter.close();
} catch (NotesException e) {
throw new WebApplicationException(ErrorHelper.createErrorResponse(e, Response.Status.BAD_REQUEST));
} catch (ServiceException e) {
throw new WebApplicationException(ErrorHelper.createErrorResponse(e, Response.Status.BAD_REQUEST));
} finally {
if (view != null) {
try {
view.recycle();
view = null;
} catch(NotesException ex) {
DAS_LOGGER.warn(ex, "Exception caught and ignored."); // $NLW-ViewEntryCollectionResource.Exceptioncaughtandignored-1$
}
}
}
}
};
builder.type(MediaType.APPLICATION_JSON_TYPE).entity(streamJsonEntity);
Response response = builder.build();
streamJsonEntity.setResponse(response);
return response;
}
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response postViewEntry(@Context final UriInfo uriInfo,
@PathParam("key") final String keyName, // $NON-NLS-1$
@PathParam("value") final String keyValue, // $NON-NLS-1$
String requestEntity,
@QueryParam(PARAM_VIEW_FORM) String form,
@QueryParam(PARAM_VIEW_COMPUTEWITHFORM) String computeWithForm,
@QueryParam(PARAM_VIEW_PARENTID) String parentId) {
DAS_LOGGER.traceEntry(this, "postViewEntry"); // $NON-NLS-1$
DataService.beforeRequest(FEATURE_REST_API_DATA_VIEW_ENTRIES, STAT_VIEW_ENTRIES);
URI location = null;
View view = null;
try {
Database database = this.getDatabase(DB_ACCESS_VIEWS);
view = getCurrentView(keyName, keyValue, database);
JsonJavaObject jsonItems;
JsonJavaFactory factory = JsonJavaFactory.instanceEx;
try {
StringReader reader = new StringReader(requestEntity);
try {
jsonItems = (JsonJavaObject)JsonParser.fromJson(factory, reader);
} finally {
reader.close();
}
} catch(Exception ex) {
throw new ServiceException(ex, "Error while parsing the JSON content"); // $NLX-ViewEntryCollectionResource.ErrorwhileparsingtheJSONcontent-1$
}
Document document = null;
RestDocumentNavigator docNav = null;
try {
DefaultViewParameters parameters = new DefaultViewParameters();
parameters.setGlobalValues(DefaultViewParameters.GLOBAL_ALL);
parameters.setSystemColumns(DefaultViewParameters.SYSCOL_ALL);
parameters.setDefaultColumns(true);
// Get a view navigator to get access to the columns
RestViewNavigator viewNav = RestViewNavigatorFactory.createNavigatorForDesign(view,parameters);
// Get a document docNav
docNav = RestDocumentNavigatorFactory.createNavigator(view, parameters);
// Create a new document.
docNav.createDocument();
document = docNav.getDocument();
JsonViewEntryCollectionContent content = new JsonViewEntryCollectionContent(view);
content.updateFields(viewNav, docNav, jsonItems);
// Handle parameters.
if (StringUtil.isNotEmpty(form)) {
document.replaceItemValue(ITEM_FORM, form);
}
if (StringUtil.isNotEmpty(parentId)) {
Document parent = null;
try {
parent = database.getDocumentByUNID(parentId);
document.makeResponse(parent);
}
catch (NotesException e) {
throw new ServiceException(e, "Error creating document."); // $NLX-ViewEntryCollectionResource.Errorcreatingdocument-1$
} finally {
if ( parent != null ) {
try {
parent.recycle();
} catch (NotesException e) {
Loggers.SERVICES_LOGGER.traceDebug("Exception thrown on recycle.", e); // $NON-NLS-1$
}
parent = null;
}
}
}
if (StringUtil.isNotEmpty(computeWithForm)&& computeWithForm.compareToIgnoreCase(PARAM_VALUE_TRUE) == 0) {
document.computeWithForm(true, true);
}
document.save();
if (view.isFolder())
document.putInFolder(view.getName());
URI baseDocumentUri= uriInfo.getAbsolutePath();
baseDocumentUri = UriHelper.trimAtLast(baseDocumentUri, PARAM_COLLECTIONS);
baseDocumentUri = UriHelper.appendPathSegment(baseDocumentUri, PARAM_DOCUMENTS);
baseDocumentUri = UriHelper.appendPathSegment(baseDocumentUri, PARAM_UNID);
location = UriHelper.appendPathSegment(baseDocumentUri, document.getUniversalID());
} catch(Throwable ex) {
if(ex instanceof ServiceException) {
throw (ServiceException)ex;
}
throw new ServiceException(ex,"Error while creating document."); // $NLX-ViewEntryCollectionResource.Errorwhilecreatingdocument-1$
} finally {
// The call to docNav.recycle will recycle the document.
if (docNav != null)
docNav.recycle();
}
}
catch (ServiceException e) {
throw new WebApplicationException(ErrorHelper.createErrorResponse(e, Response.Status.BAD_REQUEST));
}
finally {
if (view != null) {
try {
view.recycle();
view = null;
} catch(NotesException ex) {
DAS_LOGGER.warn(ex, "Exception caught and ignored."); // $NLW-ViewEntryCollectionResource.Exceptioncaughtandignored.1-1$
}
}
}
ResponseBuilder builder = Response.created(location);
// builder.type(MediaType.APPLICATION_JSON_TYPE).entity(jsonEntity);
Response response = builder.build();
DAS_LOGGER.traceExit(this, "postViewEntry", response); // $NON-NLS-1$
return response;
}
@PUT
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response putFolderOperations(
String requestEntity,
@PathParam("key") final String keyName, // $NON-NLS-1$
@PathParam("value") final String keyValue) { // $NON-NLS-1$
DAS_LOGGER.traceEntry(this, "putFolderOperations"); // $NON-NLS-1$
DataService.beforeRequest(FEATURE_REST_API_DATA_VIEW_ENTRIES, STAT_VIEW_ENTRIES);
View view = null;
try {
// Open the folder
Database database = this.getDatabase(DB_ACCESS_VIEWS);
view = getCurrentView(keyName, keyValue, database);
// Make sure it really is a folder
if ( ! view.isFolder() ) {
throw new WebApplicationException(ErrorHelper.createErrorResponse("Cannot perform folder operations on a view.", Response.Status.BAD_REQUEST)); // $NLX-ViewEntryCollectionResource.Cannotperformfolderoperationsonav-1$
}
// Parse the JSON input
JsonJavaObject jsonOperations = null;
try {
StringReader reader = new StringReader(requestEntity);
try {
jsonOperations = (JsonJavaObject)JsonParser.fromJson(JsonJavaFactory.instanceEx, reader);
}
finally {
reader.close();
}
}
catch(Exception ex) {
throw new ServiceException(ex, "Error while parsing the JSON content"); // $NLX-ViewEntryCollectionResource.ErrorwhileparsingtheJSONcontent.1-1$
}
// Delegate the work
JsonViewEntryCollectionContent content = new JsonViewEntryCollectionContent(view);
content.updateFolder(jsonOperations);
}
catch (ServiceException e) {
throw new WebApplicationException(ErrorHelper.createErrorResponse(e, Response.Status.BAD_REQUEST));
} catch (NotesException e) {
throw new WebApplicationException(ErrorHelper.createErrorResponse(e, Response.Status.INTERNAL_SERVER_ERROR));
}
finally {
if (view != null) {
try {
view.recycle();
view = null;
} catch(NotesException ex) {
DAS_LOGGER.warn(ex, "Exception caught and ignored."); // $NLW-ViewEntryCollectionResource.Exceptioncaughtandignored.2-1$
}
}
}
ResponseBuilder builder = Response.ok();
Response response = builder.build();
DAS_LOGGER.traceExit(this, "putFolderOperations", response); // $NON-NLS-1$
return response;
}
}