/* * � 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.services.content; import static com.ibm.domino.services.rest.RestServiceConstants.*; import static com.ibm.domino.services.util.ContentUtil.getContentRangeHeaderString; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Vector; 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.JsonException; import com.ibm.commons.util.io.json.JsonJavaObject; import com.ibm.domino.services.ServiceException; import com.ibm.domino.services.rest.das.RestDocumentNavigator; import com.ibm.domino.services.rest.das.view.RestViewColumn; 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.RestViewService; import com.ibm.domino.services.rest.das.view.ViewParameters; import com.ibm.domino.services.util.JsonWriter; //import com.ibm.domino.services.util.ContentUtil; public class JsonViewEntryCollectionContent extends JsonContent { public static final int GLOBAL_ALL = 0xFFFFFFFF; private static final String FOLDER_OP_ADD = "add"; //$NON-NLS-1$ private static final String FOLDER_OP_REMOVE = "remove"; //$NON-NLS-1$ private View _view; private RestViewService _service; private RestViewNavigator _navigator; private String _baseUri; private String _baseDocumentUri; public JsonViewEntryCollectionContent(View view) { _view = view; } public JsonViewEntryCollectionContent(View view, String baseUri, String baseDocumentUri) { _view = view; _baseUri = baseUri; _baseDocumentUri = baseDocumentUri; } public JsonViewEntryCollectionContent(View view, RestViewService service) { _view = view; _service = service; } /** * Get content range header for a view entry collection. * * @param parameters * @return The HEADER_CONTENT_RANGE used for response header. * @throws ServiceException */ public String getContentRangeHeader(ViewParameters parameters) throws ServiceException { String contentRangeHeader = null; try { // Create the new XPages view navigator if (_navigator == null) { _navigator = RestViewNavigatorFactory.createNavigator(_view, parameters); } int global = parameters.getGlobalValues(); if((global & ViewParameters.GLOBAL_ENTRIES)!=0) { // Read all the entries int start = parameters.getStart(); int count = parameters.getCount(); // The TopLevelEntryCount can be expensive. _navigator.first(start,count); if((global & ViewParameters.GLOBAL_TOPLEVEL)!=0) { // Header returned to the client: // Content-Range: items 0-24/66 int entryCount = _navigator.getTopLevelEntryCount(); if(entryCount>0 && start<entryCount) { int last = entryCount-1<count+start-1?entryCount-1:count+start-1; contentRangeHeader = getContentRangeHeaderString(start, last, entryCount); } } } } catch(Exception ex) { throw new ServiceException(ex,""); // $NON-NLS-1$ } return contentRangeHeader; } /** * Write JSON for a view entry collection. * * @param jsonWriter * @param parameters * @throws ServiceException */ public void writeViewEntryCollection(JsonWriter jsonWriter, ViewParameters parameters) throws ServiceException { try { // Create the new XPages view navigator if (_navigator == null) { _navigator = RestViewNavigatorFactory.createNavigator(_view, parameters); } try { int global = parameters.getGlobalValues(); if((global & ViewParameters.GLOBAL_TIMESTAMP)!=0) { // Should go to the HTTP header // jsonWriter.startProperty("@timestamp"); // jsonWriter.outDateLiteral(new Date()); // jsonWriter.endProperty(); } if((global & ViewParameters.GLOBAL_ENTRIES)!=0) { jsonWriter.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=_navigator.first(start,count); b && idx<count; b=_navigator.next(), idx++) { jsonWriter.startArrayItem(); writeEntryAsJson(jsonWriter,syscol,defColumns,columns,_navigator,_baseUri,_baseDocumentUri); jsonWriter.endArrayItem(); } jsonWriter.endArray(); } } finally { if (_navigator != null) { _navigator.recycle(); } } } catch(UnsupportedEncodingException ex) { throw new ServiceException(ex,""); // $NON-NLS-1$ } catch(IOException ex) { throw new ServiceException(ex,""); // $NON-NLS-1$ } } protected void writeEntryAsJson(JsonWriter jsonWriter, int syscol, boolean defColumns, List<RestViewColumn> columns, RestViewNavigator navigator, String baseUri, String baseDocumentUri) throws IOException, ServiceException { jsonWriter.startObject(); writeSystemColumns(jsonWriter, syscol, navigator, false, baseUri, baseDocumentUri); writeColumns(jsonWriter, defColumns, columns, navigator); jsonWriter.endObject(); } protected void writeColumns(JsonWriter jsonWriter, boolean defColumns, List<RestViewColumn> columns, RestViewNavigator navigator) throws IOException, ServiceException { int colIdx = 0; // Read the default columns if(defColumns) { int colCount = navigator.getColumnCount(); for(int i=0; i<colCount; i++) { String colName = navigator.getColumnName(i); Object colValue = navigator.getColumnValue(i); writeColumn(jsonWriter, navigator, 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(_service, navigator); writeCustomColumn(jsonWriter, navigator, colIdx++, colName, colValue, c); } } } protected void writeSystemColumns(JsonWriter jsonWriter, int syscol, RestViewNavigator navigator, boolean forceDefaultAttributes, String baseUri, String baseDocumentUri) throws IOException, ServiceException { // write the system columns if ((syscol & ViewParameters.SYSCOL_HREF)!=0 && baseUri != null && !navigator.isCategory() && navigator.isDocument()) { jsonWriter.startProperty(ATTR_HREF); jsonWriter.outStringLiteral(baseUri + "/" + navigator.getUniversalId()); // $NON-NLS-1$ jsonWriter.endProperty(); } if ((syscol & ViewParameters.SYSCOL_LINK)!=0 && baseDocumentUri != null && !navigator.isCategory() && navigator.isDocument()) { jsonWriter.startProperty(ATTR_LINK); jsonWriter.startObject(); jsonWriter.startProperty(ATTR_LINK_REL); jsonWriter.outStringLiteral("document"); // TODO: Check Vulcan guidelines $NON-NLS-1$ jsonWriter.endProperty(); jsonWriter.startProperty(ATTR_LINK_HREF); jsonWriter.outStringLiteral(baseDocumentUri + "/" + navigator.getUniversalId()); // $NON-NLS-1$ jsonWriter.endProperty(); jsonWriter.endObject(); jsonWriter.endProperty(); } if(true) { // Always write the entry id jsonWriter.startProperty(ATTR_ENTRYID); //jsonWriter.outStringLiteral(getEntryId(navigator)); jsonWriter.outStringLiteral(navigator.getPosition()+"-"+navigator.getUniversalId()); // $NON-NLS-1$ jsonWriter.endProperty(); } if((syscol & ViewParameters.SYSCOL_UNID)!=0) { jsonWriter.startProperty(ATTR_UNID); jsonWriter.outStringLiteral(navigator.getUniversalId()); jsonWriter.endProperty(); } if((syscol & ViewParameters.SYSCOL_NOTEID)!=0) { jsonWriter.startProperty(ATTR_NOTEID); jsonWriter.outStringLiteral(navigator.getNoteId()); jsonWriter.endProperty(); } if((syscol & ViewParameters.SYSCOL_POSITION)!=0) { jsonWriter.startProperty(ATTR_POSITION); jsonWriter.outStringLiteral(navigator.getPosition()); jsonWriter.endProperty(); } if((syscol & ViewParameters.SYSCOL_READ)!=0) { boolean read = navigator.getRead(); if(forceDefaultAttributes || read) { jsonWriter.startProperty(ATTR_READ); jsonWriter.outBooleanLiteral(true); jsonWriter.endProperty(); } } if((syscol & ViewParameters.SYSCOL_SIBLINGS)!=0) { int count = navigator.getSiblings(); if(forceDefaultAttributes || count>0) { jsonWriter.startProperty(ATTR_SIBLINGS); jsonWriter.outIntLiteral(count); jsonWriter.endProperty(); } } if((syscol & ViewParameters.SYSCOL_DESCENDANTS)!=0) { int count = navigator.getDescendants(); if(forceDefaultAttributes || count>0) { jsonWriter.startProperty(ATTR_DESCENDANTS); jsonWriter.outIntLiteral(count); jsonWriter.endProperty(); } } if((syscol & ViewParameters.SYSCOL_CHILDREN)!=0) { int count = navigator.getChildren(); if(forceDefaultAttributes || count>0) { jsonWriter.startProperty(ATTR_CHILDREN); jsonWriter.outIntLiteral(count); jsonWriter.endProperty(); } } if((syscol & ViewParameters.SYSCOL_INDENT)!=0) { int indent = navigator.getIndent(); if(forceDefaultAttributes || indent>0) { jsonWriter.startProperty(ATTR_INDENT); jsonWriter.outIntLiteral(indent); jsonWriter.endProperty(); } } // Added by Tim Tripcony // Expect it to disappear but hope it will remain if((syscol & ViewParameters.SYSCOL_FORM)!=0) { String form = navigator.getForm(); if(forceDefaultAttributes || !StringUtil.isEmpty(form)) { jsonWriter.startProperty(ATTR_FORM); jsonWriter.outStringLiteral(form); jsonWriter.endProperty(); } } if((syscol & ViewParameters.SYSCOL_CATEGORY)!=0) { boolean category = navigator.isCategory(); if(forceDefaultAttributes || category) { jsonWriter.startProperty(ATTR_CATEGORY); jsonWriter.outBooleanLiteral(category); jsonWriter.endProperty(); } } if((syscol & ViewParameters.SYSCOL_RESPONSE)!=0) { boolean response = navigator.isResponse(); if(forceDefaultAttributes || response) { jsonWriter.startProperty(ATTR_RESPONSE); jsonWriter.outBooleanLiteral(response); jsonWriter.endProperty(); } } } protected void writeColumn(JsonWriter jsonWriter, RestViewNavigator navigator, int colIdx, String colName, Object colValue) throws IOException, ServiceException { writeDominoProperty(jsonWriter, colName, colValue); } protected void writeCustomColumn(JsonWriter jsonWriter, RestViewNavigator navigator, int colIdx, String colName, Object colValue, RestViewColumn column) throws IOException, ServiceException { writeDominoProperty(jsonWriter, colName, colValue); } /** * Update fields in view entry collection. * * @param viewNav * @param docNav * @param items * @throws ServiceException * @throws JsonException * @throws IOException */ public 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); if(fieldName!=null) { // Ignore columns that are not based on fields Object value = items.get(columnName); updateField(viewNav, docNav, items, columnName, fieldName, value); } } } @SuppressWarnings("unchecked") // $NON-NLS-1$ private 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); } } 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(viewNav.getParameters().isDefaultColumns()) { itemName = viewNav.getItemName(columnName); } // Look for a custom column pointing to an actual column if(itemName==null) { List<RestViewColumn> cols = viewNav.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) { itemsMap.put(columnName, itemName); } return itemName; } /** * Updates a folder (adds and/or removes documents) * * <p>TODO: Some of this code is duplicated in RestViewJsonService. We need * to make RestViewJsonService use this code instead. * * @param folderOperations * @throws ServiceException */ public void updateFolder(JsonJavaObject folderOperations) throws ServiceException { Map<String, String> operations = getOperationsMap(folderOperations); Database db = null; String folderName = null; int errors = 0; try { db = _view.getParent(); folderName = _view.getName(); } catch (NotesException ex) { throw new ServiceException(ex, ""); // $NON-NLS-1$ } for( Map.Entry<String,String> op: operations.entrySet() ) { Document doc = null; String docunid = op.getKey(); boolean add = (op.getValue().equalsIgnoreCase(FOLDER_OP_ADD)); try { doc = db.getDocumentByUNID(docunid); if (add) doc.putInFolder(folderName); else doc.removeFromFolder(folderName); } catch (NotesException ex) { // Keep a count of errors. Arguably, it's better to keep going // through a large set of changes than it is to abort. This at // least makes the result deterministic. errors++; } if (doc != null) { try { doc.recycle(); } catch (NotesException ex) { // Ignore this } doc = null; } } if ( errors > 0 ) { throw new ServiceException(new NotesException(), "Error(s) performing {0} of {1} folder operation(s).", errors, operations.size()); // $NLX-JsonViewEntryCollectionContent.Errorsperforming0of1folderoperati-1$ } } private Map<String, String> getOperationsMap (JsonJavaObject folderOperations) { Map<String, String> docOperations = new HashMap<String, String>(); for(Iterator<String> it = folderOperations.getJsonProperties(); it.hasNext(); ) { String opName = it.next(); if ((opName.equalsIgnoreCase(FOLDER_OP_ADD)) || (opName.equalsIgnoreCase(FOLDER_OP_REMOVE))) { Object value = folderOperations.get(opName); if (value instanceof List) { Vector<?> vector = new Vector((List)value); if (!vector.isEmpty()) { for(Iterator<?> docs = vector.iterator(); docs.hasNext(); ) { String docunid = (String) docs.next(); if (docunid.length() > 0) docOperations.put(docunid, opName); } } } } } return docOperations; } }