/* * � Copyright IBM Corp. 2010 * * 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.services.rest.das.view; import static com.ibm.domino.services.ResponseCode.RSRC_CREATED; import static com.ibm.domino.services.rest.RestParameterConstants.PARAM_VIEW_CATEGORY; import static com.ibm.domino.services.rest.RestParameterConstants.PARAM_VIEW_COUNT; import static com.ibm.domino.services.rest.RestParameterConstants.PARAM_VIEW_EXPANDLEVEL; import static com.ibm.domino.services.rest.RestParameterConstants.PARAM_VIEW_PAGEINDEX; import static com.ibm.domino.services.rest.RestParameterConstants.PARAM_VIEW_PAGESIZE; import static com.ibm.domino.services.rest.RestParameterConstants.PARAM_VIEW_SEARCH; import static com.ibm.domino.services.rest.RestParameterConstants.PARAM_VIEW_SORTCOLUMN; import static com.ibm.domino.services.rest.RestParameterConstants.PARAM_VIEW_SORTORDER; import static com.ibm.domino.services.rest.RestParameterConstants.PARAM_VIEW_START; import static com.ibm.domino.services.rest.RestParameterConstants.PARAM_VIEW_STARTINDEX; import static com.ibm.domino.services.rest.RestParameterConstants.PARAM_VIEW_STARTKEYS; import static com.ibm.domino.services.rest.RestServiceConstants.*; import static com.ibm.domino.services.HttpServiceConstants.*; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Reader; import java.io.UnsupportedEncodingException; import java.io.Writer; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Vector; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import lotus.domino.Document; import lotus.domino.NotesException; import com.ibm.commons.util.StringUtil; import com.ibm.commons.util.io.json.JsonException; 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.services.ServiceException; import com.ibm.domino.services.rest.das.RestDocumentNavigator; import com.ibm.domino.services.rest.das.RestDocumentNavigatorFactory; import com.ibm.domino.services.util.JsonWriter; /** * Domino View Service. */ public class RestViewItemFileService extends RestViewService { // Indicated if the attributes should be written when they have a default value // for example, when a count==0 protected boolean forceDefaultAttributes; public RestViewItemFileService(HttpServletRequest httpRequest, HttpServletResponse httpResponse, ViewParameters parameters) { super(httpRequest, httpResponse,parameters); } @Override public void renderService() throws ServiceException { if (HTTP_GET.equalsIgnoreCase(getHttpRequest().getMethod())) { renderServiceJSONGet(); } else if (HTTP_POST.equalsIgnoreCase(getHttpRequest().getMethod())) { renderServiceJSONPost(); } else { // Use a different status for an error? //HttpServletResponse.SC_METHOD_NOT_ALLOWED; throw new ServiceException(null,"Method {0} is not allowed with JSON format",getHttpRequest().getMethod()); // $NLX-RestViewItemFileService.Method0isnotallowedwithJSONformat-1$ } } // ========================================================================== // Access to the parameters from the request // ========================================================================== @Override protected ViewParameters wrapViewParameters(ViewParameters parameters) { return new RequestViewParameter(parameters); } protected class RequestViewParameter extends ViewParametersDelegate { private boolean ignoreRequestParams; private int start; private int count; protected RequestViewParameter(ViewParameters delegate) { super(delegate); this.ignoreRequestParams = delegate.isIgnoreRequestParams(); String param = getHttpRequest().getParameter(PARAM_VIEW_START); if (StringUtil.isNotEmpty(param)) { try { start = Integer.parseInt(param); } catch (NumberFormatException nfe) {} } else { start = delegate.getStart(); } param = getHttpRequest().getParameter(PARAM_VIEW_COUNT); if (StringUtil.isNotEmpty(param)) { try { count = Integer.parseInt(param); } catch (NumberFormatException nfe) {} } else { count = delegate.getCount(); } // The following three parameters page, ps and si map to start and count. // count = ps // start = ps * page + si param = getHttpRequest().getParameter(PARAM_VIEW_PAGESIZE); if (StringUtil.isNotEmpty(param)) { try { count = Integer.parseInt(param); } catch (NumberFormatException nfe) {} } /*else { count = delegate.getPageSize(); }*/ param = getHttpRequest().getParameter(PARAM_VIEW_PAGEINDEX); if (StringUtil.isNotEmpty(param)) { try { int page = Integer.parseInt(param); if (page > 0) page-=1; start = page * count; } catch (NumberFormatException nfe) {} } /*else { start = delegate.getPageIndex(); } */ param = getHttpRequest().getParameter(PARAM_VIEW_STARTINDEX); if (StringUtil.isNotEmpty(param)) { try { int si = Integer.parseInt(param); start += si; } catch (NumberFormatException nfe) {} } /*else { start = delegate.getStartIndex(); }*/ } @Override public boolean isIgnoreRequestParams() { return ignoreRequestParams; } @Override public int getStart() { return start; } @Override public int getCount() { return count; } @Override public String getSortColumn() { if(!isIgnoreRequestParams()) { String param = getHttpRequest().getParameter(PARAM_VIEW_SORTCOLUMN); if (StringUtil.isNotEmpty(param)) { return param; } } return super.getSortColumn(); } @Override public String getSortOrder() { if(!isIgnoreRequestParams()) { String param = getHttpRequest().getParameter(PARAM_VIEW_SORTORDER); if (StringUtil.isNotEmpty(param)) { return param; } } return super.getSortOrder(); } @Override public String getCategoryFilter() { if(!isIgnoreRequestParams()) { String param = getHttpRequest().getParameter(PARAM_VIEW_CATEGORY); if (StringUtil.isNotEmpty(param)) { return param; } } return super.getCategoryFilter(); } @Override public int getExpandLevel() { if(!isIgnoreRequestParams()) { String param = getHttpRequest().getParameter(PARAM_VIEW_EXPANDLEVEL); if (StringUtil.isNotEmpty(param)) { try { return Integer.parseInt(param); } catch (NumberFormatException nfe) {} } } return super.getExpandLevel(); } @Override public String getSearch() { if(!isIgnoreRequestParams()) { String param = getHttpRequest().getParameter(PARAM_VIEW_SEARCH); if (StringUtil.isNotEmpty(param)) { return param; } } return super.getSearch(); } @Override public Object getStartKeys() { if(!isIgnoreRequestParams()) { String param = getHttpRequest().getParameter(PARAM_VIEW_STARTKEYS); if (StringUtil.isNotEmpty(param)) { return param; } } return super.getStartKeys(); } } // ========================================================================== // GET: read the data // ========================================================================== protected void renderServiceJSONGet() throws ServiceException { try { ViewParameters parameters = getParameters(); String contentType = parameters.getContentType(); if(StringUtil.isEmpty(contentType)) { contentType = CONTENTTYPE_APPLICATION_JSON; } getHttpResponse().setContentType(contentType); getHttpResponse().setCharacterEncoding(ENCODING_UTF8); Writer writer = new OutputStreamWriter(getOutputStream(),ENCODING_UTF8); boolean compact = parameters.isCompact(); JsonWriter g = new JsonWriter(writer,compact); // Create the new XPages view navigator RestViewNavigator nav = RestViewNavigatorFactory.createNavigator(this.getView(),parameters); try { // Start the main Object g.startObject(); // writer. int global = parameters.getGlobalValues(); if((global & ViewParameters.GLOBAL_TIMESTAMP)!=0) { g.startProperty(ATTR_TIMESTAMP); g.outDateLiteral(new Date()); g.endProperty(); } if((global & ViewParameters.GLOBAL_TOPLEVEL)!=0) { g.startProperty(ATTR_TOPLEVELENTRIES); g.outIntLiteral(nav.getTopLevelEntryCount()); g.endProperty(); } if((global & ViewParameters.GLOBAL_ENTRIES)!=0) { g.startProperty(ATTR_ITEMS); g.startArray(); // Read all the entries int start = parameters.getStart(); int count = parameters.getCount(); int syscol = parameters.getSystemColumns(); boolean defColumns = parameters.isDefaultColumns(); List<RestViewColumn> columns = parameters.getColumns(); int idx = 0; for( boolean b=nav.first(start,count); b && idx<count; b=nav.next(), idx++) { g.startArrayItem(); writeEntryAsJson(g,syscol,defColumns,columns,nav); g.endArrayItem(); } g.endArray(); g.endProperty(); } // Terminate the main object g.endObject(); } finally { nav.recycle(); } writer.flush(); } catch(UnsupportedEncodingException ex) { throw new ServiceException(ex,""); // $NON-NLS-1$ } catch(IOException ex) { throw new ServiceException(ex,""); // $NON-NLS-1$ } catch (NotesException ex) { throw new ServiceException(ex,""); // $NON-NLS-1$ } } protected void writeEntryAsJson(JsonWriter g, int syscol, boolean defColumns, List<RestViewColumn> columns, RestViewNavigator nav) throws IOException, ServiceException { g.startObject(); writeSystemColumns(g, syscol, nav); writeColumns(g, defColumns, columns, nav); g.endObject(); } protected void writeColumns(JsonWriter g, boolean defColumns, List<RestViewColumn> columns, RestViewNavigator nav) throws IOException, ServiceException { int colIdx = 0; // Read the default columns if(defColumns) { int colCount = nav.getColumnCount(); for(int i=0; i<colCount; i++) { String colName = nav.getColumnName(i); Object colValue = nav.getColumnValue(i); writeColumn(g, nav, colIdx++, colName, colValue); } } // Calculate the extra columns int ccount = columns!=null ? columns.size() : 0; if(ccount>0) { for( int i=0; i<ccount; i++) { RestViewColumn c = columns.get(i); String colName = c.getName(); Object colValue = c.evaluate(this, nav); writeCustomColumn(g, nav, colIdx++, colName, colValue, c); } } } protected void writeSystemColumns(JsonWriter g, int syscol, RestViewNavigator nav) throws IOException, ServiceException { // write the system columns if(true) { // Always write the entry id g.startProperty(ATTR_ENTRYID); g.outStringLiteral(getEntryId(nav)); g.endProperty(); } if((syscol & ViewParameters.SYSCOL_UNID)!=0) { g.startProperty(ATTR_UNID); g.outStringLiteral(nav.getUniversalId()); g.endProperty(); } if((syscol & ViewParameters.SYSCOL_NOTEID)!=0) { g.startProperty(ATTR_NOTEID); g.outStringLiteral(nav.getNoteId()); g.endProperty(); } if((syscol & ViewParameters.SYSCOL_POSITION)!=0) { g.startProperty(ATTR_POSITION); g.outStringLiteral(nav.getPosition()); g.endProperty(); } if((syscol & ViewParameters.SYSCOL_READ)!=0) { boolean read = nav.getRead(); if(forceDefaultAttributes || read) { g.startProperty(ATTR_READ); g.outBooleanLiteral(true); g.endProperty(); } } if((syscol & ViewParameters.SYSCOL_SIBLINGS)!=0) { int count = nav.getSiblings(); if(forceDefaultAttributes || count>0) { g.startProperty(ATTR_SIBLINGS); g.outIntLiteral(count); g.endProperty(); } } if((syscol & ViewParameters.SYSCOL_DESCENDANTS)!=0) { int count = nav.getDescendants(); if(forceDefaultAttributes || count>0) { g.startProperty(ATTR_DESCENDANTS); g.outIntLiteral(count); g.endProperty(); } } if((syscol & ViewParameters.SYSCOL_CHILDREN)!=0) { int count = nav.getChildren(); if(forceDefaultAttributes || count>0) { g.startProperty(ATTR_CHILDREN); g.outIntLiteral(count); g.endProperty(); } } if((syscol & ViewParameters.SYSCOL_INDENT)!=0) { int indent = nav.getIndent(); if(forceDefaultAttributes || indent>0) { g.startProperty(ATTR_INDENT); g.outIntLiteral(indent); g.endProperty(); } } } protected void writeColumn(JsonWriter g, RestViewNavigator nav, int colIdx, String colName, Object colValue) throws IOException, ServiceException { if(colValue!=null) { g.startProperty(colName); g.outDominoValue(colValue); g.endProperty(); } } protected void writeCustomColumn(JsonWriter g, RestViewNavigator nav, int colIdx, String colName, Object colValue, RestViewColumn column) throws IOException, ServiceException { if(colValue!=null) { g.startProperty(colName); g.outDominoValue(colValue); g.endProperty(); } } // ========================================================================== // POST: update the data // ========================================================================== protected void renderServiceJSONPost() throws ServiceException { ViewParameters parameters = getParameters(); // Look if the request seems correct String reqContentType = getHttpRequest().getContentType(); if(!reqContentType.contains(CONTENTTYPE_APPLICATION_JSON)) { throw new ServiceException(null,"Request does not contains 'application/json' but {0}",reqContentType); // $NLX-RestViewItemFileService.Requestdoesnotcontainsapplication-1$ } JsonJavaFactory factory = JsonJavaFactory.instanceEx; // Ok, parse the JSON content JsonJavaObject json; try { Reader r = getHttpRequest().getReader(); try { json = (JsonJavaObject)JsonParser.fromJson(factory, r); } finally { r.close(); } } catch(Exception ex) { throw new ServiceException(ex,"Error while parsing the JSON content"); // $NLX-RestViewItemFileService.ErrorwhileparsingtheJSONcontent-1$ } RestDocumentNavigator docNav = null; try { // Get a view navigator to get access to the columns RestViewNavigator viewNav = RestViewNavigatorFactory.createNavigatorForDesign(RestViewItemFileService.this.getView(),parameters); // Get a document docNav docNav = RestDocumentNavigatorFactory.createNavigator(this.getView(),getParameters()); // Start the transaction boolean supportsTransaction = docNav.supportsTransaction(); // Now, browse the different entries List<JsonJavaObject> rows = (List<JsonJavaObject>)json.get(JSON_OBJECT_PROPERTY_ROWS); if(rows!=null) { try { for(JsonJavaObject row: rows) { int op = row.getInt(JSON_OBJECT_PROPERTY_OP); String id = row.getString(JSON_OBJECT_PROPERTY_ID); id = getEntryUNID(id); JsonJavaObject items = row.getJsonObject(JSON_OBJECT_PROPERTY_ITEMS); processRow(viewNav, docNav, op, id, items); } if(supportsTransaction) { docNav.commit(); } } catch(Throwable ex) { if(supportsTransaction) { docNav.rollback(); } if(ex instanceof ServiceException) { throw (ServiceException)ex; } throw new ServiceException(ex,"Error while updating data"); // $NLX-RestViewItemFileService.Errorwhileupdatingdata-1$ } } } catch (NotesException ex) { throw new ServiceException(ex,""); // $NON-NLS-1$ } finally { if (docNav != null) docNav.recycle(); } try { String contentType = parameters.getContentType(); if(StringUtil.isEmpty(contentType)) { contentType = CONTENTTYPE_TEXT_HTML; } getHttpResponse().setContentType(contentType); getHttpResponse().setCharacterEncoding(ENCODING_UTF8); Writer writer = new OutputStreamWriter(getOutputStream(),ENCODING_UTF8); writer.write("{}"); // $NON-NLS-1$ writer.flush(); } catch(UnsupportedEncodingException ex) { throw new ServiceException(ex,""); // $NON-NLS-1$ } catch(IOException ex) { throw new ServiceException(ex,""); // $NON-NLS-1$ } } protected void processRow(RestViewNavigator viewNav, RestDocumentNavigator docNav, int op, String id, JsonJavaObject items) throws ServiceException, JsonException, IOException { switch(op) { case 0: createDocument(viewNav, docNav, items); break; case 1: updateDocument(viewNav, docNav, id,items); break; case 2: deleteDocument(viewNav, docNav, id, items); break; } } protected void createDocument(RestViewNavigator viewNav, RestDocumentNavigator docNav, JsonJavaObject items) throws ServiceException, JsonException, IOException { if(!queryNewDocument()) { throw new ServiceException(null, msgErrorCreatingDocument()); } docNav.createDocument(); Document doc = docNav.getDocument(); postNewDocument(doc); try { updateFields(viewNav, docNav, items); String form = getParameters().getFormName(); if (StringUtil.isNotEmpty(form)) { docNav.replaceItemValue(ITEM_FORM, form); } if (getParameters().isComputeWithForm()) { docNav.computeWithForm(); } if(!querySaveDocument(doc)) { throw new ServiceException(null, msgErrorCreatingDocument()); } docNav.save(); postSaveDocument(doc); getHttpResponse().setStatus(RSRC_CREATED.httpStatusCode); } finally { docNav.recycle(); } } private String msgErrorCreatingDocument() { return "Error creating document."; // $NLX-RestViewItemFileService.Errorcreatingdocument.1-1$ } protected void updateDocument(RestViewNavigator viewNav, RestDocumentNavigator docNav, String id, JsonJavaObject items) throws ServiceException, JsonException, IOException { if(!queryOpenDocument(id)) { throw new ServiceException(null, msgErrorUpdatingData()); } docNav.openDocument(id); Document doc = docNav.getDocument(); postOpenDocument(doc); try { updateFields(viewNav, docNav, items); if (getParameters().isComputeWithForm()) { docNav.computeWithForm(); } if(!querySaveDocument(doc)) { throw new ServiceException(null, msgErrorUpdatingData()); } docNav.save(); postSaveDocument(doc); } finally { docNav.recycle(); } } /** * @return */ private String msgErrorUpdatingData() { return "Error updating data."; // $NLX-RestViewItemFileService.Errorupdatingdata.1-1$ } protected void updateFields(RestViewNavigator viewNav, RestDocumentNavigator docNav, JsonJavaObject items) throws ServiceException, JsonException, IOException { for(Iterator<String> it = items.getJsonProperties(); it.hasNext(); ) { String columnName = it.next(); String fieldName = findItemName(viewNav, columnName); Object value = items.get(columnName); updateField(viewNav, docNav, items, columnName, fieldName, value); } } @SuppressWarnings("unchecked") // $NON-NLS-1$ protected void updateField(RestViewNavigator viewNav, RestDocumentNavigator docNav, JsonJavaObject items, String columnName, String fieldName, Object value) throws ServiceException, JsonException, IOException { if (value instanceof List) { Vector<?> vector = new Vector((List)value); docNav.replaceItemValue(fieldName, vector); } else { docNav.replaceItemValue(fieldName, value); } } protected void deleteDocument(RestViewNavigator viewNav, RestDocumentNavigator docNav, String id, JsonJavaObject items) throws ServiceException, JsonException, IOException { if(!queryDeleteDocument(id)) { throw new ServiceException(null, "Error deleting document."); // $NLX-RestViewItemFileService.Errordeletingdocument-1$ } docNav.deleteDocument(id); postDeleteDocument(id); } private HashMap<String,String> itemsMap; protected String findItemName(RestViewNavigator viewNav, String columnName) throws ServiceException { if(itemsMap==null) { itemsMap = new HashMap<String, String>(); } String itemName = itemsMap.get(columnName); if(itemName!=null) { return itemName; } // Look for a predefined column if(getParameters().isDefaultColumns()) { itemName = viewNav.getItemName(columnName); } // Look for a custom column pointing to an actual column if(itemName==null) { List<RestViewColumn> cols = getParameters().getColumns(); if(cols!=null) { for(RestViewColumn c: cols) { if(StringUtil.equals(c.getName(),columnName)) { String colname = c.getColumnName(); if(StringUtil.isNotEmpty(colname)) { itemName = viewNav.getItemName(colname); } break; } } } } // If not found, throw an error if(itemName==null) { throw new ServiceException(null,"The column {0} doesn't map a Document field",columnName); // $NLX-RestViewItemFileService.Thecolumn0doesntmapaDocumentfield-1$ } itemsMap.put(columnName, itemName); return itemName; } }