001/* 002 * (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl-2.1.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * vpasquier <vpasquier@nuxeo.com> 016 */ 017package org.nuxeo.box.api.service; 018 019import com.fasterxml.jackson.core.JsonProcessingException; 020import com.fasterxml.jackson.databind.ObjectMapper; 021import com.google.common.collect.BiMap; 022import com.google.common.collect.HashBiMap; 023import org.nuxeo.box.api.BoxConstants; 024import org.nuxeo.box.api.adapter.BoxAdapter; 025import org.nuxeo.box.api.folder.adapter.BoxFolderAdapter; 026import org.nuxeo.box.api.marshalling.dao.BoxCollaboration; 027import org.nuxeo.box.api.marshalling.dao.BoxCollaborationRole; 028import org.nuxeo.box.api.marshalling.dao.BoxCollection; 029import org.nuxeo.box.api.marshalling.dao.BoxComment; 030import org.nuxeo.box.api.marshalling.dao.BoxFile; 031import org.nuxeo.box.api.marshalling.dao.BoxFolder; 032import org.nuxeo.box.api.marshalling.dao.BoxGroup; 033import org.nuxeo.box.api.marshalling.dao.BoxItem; 034import org.nuxeo.box.api.marshalling.dao.BoxObject; 035import org.nuxeo.box.api.marshalling.dao.BoxTypedObject; 036import org.nuxeo.box.api.marshalling.dao.BoxUser; 037import org.nuxeo.box.api.marshalling.exceptions.BoxJSONException; 038import org.nuxeo.box.api.marshalling.exceptions.BoxRestException; 039import org.nuxeo.box.api.marshalling.exceptions.NXBoxJsonException; 040import org.nuxeo.box.api.marshalling.jsonparsing.BoxJSONParser; 041import org.nuxeo.box.api.marshalling.jsonparsing.BoxResourceHub; 042import org.apache.commons.lang.StringUtils; 043import org.nuxeo.ecm.core.api.Blob; 044import org.nuxeo.ecm.core.api.CoreSession; 045import org.nuxeo.ecm.core.api.DocumentModel; 046import org.nuxeo.ecm.core.api.DocumentModelList; 047import org.nuxeo.ecm.core.api.NuxeoGroup; 048import org.nuxeo.ecm.core.api.NuxeoPrincipal; 049import org.nuxeo.ecm.core.api.security.ACE; 050import org.nuxeo.ecm.core.api.security.SecurityConstants; 051import org.nuxeo.ecm.platform.usermanager.UserManager; 052import org.nuxeo.runtime.api.Framework; 053 054import javax.ws.rs.core.Response; 055import java.util.ArrayList; 056import java.util.Collections; 057import java.util.HashMap; 058import java.util.List; 059import java.util.Map; 060 061/** 062 * Box Service Utils 063 * 064 * @since 5.9.3 065 */ 066public class BoxServiceImpl implements BoxService { 067 068 /** 069 * The mapping between Nuxeo ACLs and Box Collaboration 070 */ 071 protected final BiMap<String, String> nxBoxRole; 072 073 @Override 074 public BiMap<String, String> getNxBoxRole() { 075 return nxBoxRole; 076 } 077 078 public BoxServiceImpl() { 079 nxBoxRole = HashBiMap.create(); 080 nxBoxRole.put(SecurityConstants.EVERYTHING, BoxCollaborationRole.EDITOR); 081 nxBoxRole.put(SecurityConstants.READ, BoxCollaborationRole.VIEWER); 082 nxBoxRole.put(SecurityConstants.WRITE, BoxCollaborationRole.VIEWER_UPLOADER); 083 } 084 085 @Override 086 public BoxCollection searchBox(String term, CoreSession session, String limit, String offset) 087 { 088 final Map<String, Object> collectionProperties = new HashMap<>(); 089 StringBuilder query = new StringBuilder(); 090 query.append("SELECT * FROM " + "Document where ecm:fulltext = '" + term + "'"); 091 DocumentModelList documentModels = session.query(query.toString(), null, Long.parseLong(limit), 092 Long.parseLong(offset), false); 093 // Adapt all documents to box document listing to get all properties 094 List<BoxTypedObject> boxDocuments = new ArrayList<>(); 095 for (DocumentModel doc : documentModels) { 096 BoxAdapter boxAdapter = doc.getAdapter(BoxAdapter.class); 097 boxDocuments.add(boxAdapter.getBoxItem()); 098 } 099 collectionProperties.put(BoxCollection.FIELD_ENTRIES, boxDocuments); 100 collectionProperties.put(BoxCollection.FIELD_TOTAL_COUNT, documentModels.size()); 101 return new BoxCollection(Collections.unmodifiableMap(collectionProperties)); 102 } 103 104 @Override 105 public List<BoxTypedObject> getBoxDocumentCollection(DocumentModelList documentModels, String fields) 106 { 107 108 final List<BoxTypedObject> boxObject = new ArrayList<>(); 109 for (DocumentModel documentModel : documentModels) { 110 final Map<String, Object> documentProperties = new HashMap<>(); 111 documentProperties.put(BoxTypedObject.FIELD_ID, getBoxId(documentModel)); 112 documentProperties.put(BoxItem.FIELD_SEQUENCE_ID, getBoxSequenceId(documentModel)); 113 documentProperties.put(BoxItem.FIELD_ETAG, getBoxEtag(documentModel)); 114 documentProperties.put(BoxItem.FIELD_NAME, getBoxName(documentModel)); 115 // NX MD5 -> Box SHA1 116 if (documentModel.hasSchema("file")) { 117 Blob blob = (Blob) documentModel.getPropertyValue("file:content"); 118 if (blob != null) { 119 documentProperties.put(BoxFile.FIELD_SHA1, blob.getDigest()); 120 } 121 } 122 // This different instantiation is related to the param type 123 // which is automatically added in json payload by Box marshaller 124 // following the box object type 125 BoxTypedObject boxChild; 126 boxChild = documentModel.isFolder() ? new BoxFolder() : new BoxFile(); 127 // Depending of fields filter provided in the REST call: 128 // Properties setup (* -> all) 129 if (!"*".equals(fields) && fields != null) { 130 for (String field : fields.split(",")) { 131 boxChild.put(field, documentProperties.get(field)); 132 } 133 } else { 134 boxChild.putAll(documentProperties); 135 } 136 boxObject.add(boxChild); 137 } 138 return boxObject; 139 } 140 141 /** 142 * @param boxFolderAdapter the related box folder 143 * @param ace the specific ACE for this collaboration 144 * @return a box collaboration 145 */ 146 @Override 147 public BoxCollaboration getBoxCollaboration(BoxFolderAdapter boxFolderAdapter, ACE ace, String collaborationId) 148 { 149 Map<String, Object> boxCollabProperties = new HashMap<>(); 150 // Nuxeo acl doesn't provide id yet 151 boxCollabProperties.put(BoxCollaboration.FIELD_ID, 152 computeCollaborationId(boxFolderAdapter.getBoxItem().getId(), collaborationId)); 153 // Nuxeo acl doesn't provide created date yet 154 boxCollabProperties.put(BoxCollaboration.FIELD_CREATED_AT, null); 155 // Nuxeo acl doesn't provide modified date yet 156 boxCollabProperties.put(BoxCollaboration.FIELD_MODIFIED_AT, null); 157 158 // Creator 159 final UserManager userManager = Framework.getLocalService(UserManager.class); 160 boxCollabProperties.put(BoxCollaboration.FIELD_CREATED_BY, boxFolderAdapter.getBoxItem().getCreatedBy()); 161 162 // Nuxeo doesn't provide expiration date yet 163 boxCollabProperties.put(BoxCollaboration.FIELD_EXPIRES_AT, null); 164 // Nuxeo doesn't provide status on ACL setup (accepted...) 165 boxCollabProperties.put(BoxCollaboration.FIELD_STATUS, "active"); 166 // Nuxeo doesn't provide acknowledge date on status (see 167 // just above) 168 boxCollabProperties.put(BoxCollaboration.FIELD_ACKNOWLEGED_AT, null); 169 170 // Document itself -> a mandatory folder 171 boxCollabProperties.put(BoxCollaboration.FIELD_FOLDER, boxFolderAdapter.getMiniItem()); 172 173 // User or Group whom can access to the document 174 NuxeoPrincipal user = userManager.getPrincipal(ace.getUsername()); 175 NuxeoGroup group = userManager.getGroup(ace.getUsername()); 176 boxCollabProperties.put(BoxCollaboration.FIELD_ACCESSIBLE_BY, user != null ? fillUser(user) : fillGroup(group)); 177 178 // Box Role 179 boxCollabProperties.put(BoxCollaboration.FIELD_ROLE, nxBoxRole.get(ace.getPermission())); 180 return new BoxCollaboration(boxCollabProperties); 181 } 182 183 /** 184 * Marshalling the box object to JSON 185 */ 186 @Override 187 public String toJSONString(BoxObject boxObject) throws BoxJSONException { 188 BoxJSONParser boxJSONParser = new BoxJSONParser(new BoxResourceHub()); 189 try { 190 return boxObject.toJSONString(boxJSONParser); 191 } catch (BoxJSONException e) { 192 throw new BoxRestException("Box Parser Exception", e, Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); 193 } 194 } 195 196 /** 197 * Helpers to get Ids for sequence, etag and id itself. In case of root, sequence and etag are null and id = 0 198 * according to the box documentation. 199 */ 200 201 @Override 202 public String getBoxId(DocumentModel doc) { 203 if (doc != null) { 204 return doc.getName() != null ? doc.getId() : "0"; 205 } 206 return null; 207 } 208 209 @Override 210 public String getBoxSequenceId(DocumentModel doc) { 211 if (doc != null) { 212 return doc.getName() != null ? doc.getId() : null; 213 } 214 return null; 215 } 216 217 @Override 218 public String getBoxEtag(DocumentModel doc) { 219 if (doc != null) { 220 return doc.getName() != null ? doc.getId() + "_" + doc.getVersionLabel() : null; 221 } 222 return null; 223 } 224 225 @Override 226 public String getBoxName(DocumentModel doc) { 227 if (doc != null) { 228 return doc.getName() != null ? doc.getName() : "/"; 229 } 230 return null; 231 } 232 233 /** 234 * Return a box user from a Nuxeo user metamodel 235 */ 236 @Override 237 public BoxUser fillUser(NuxeoPrincipal creator) { 238 final Map<String, Object> mapUser = new HashMap<>(); 239 mapUser.put(BoxItem.FIELD_ID, creator != null ? creator.getPrincipalId() : "system"); 240 mapUser.put(BoxItem.FIELD_NAME, creator != null ? creator.getFirstName() + " " + creator.getLastName() 241 : "system"); 242 mapUser.put(BoxUser.FIELD_LOGIN, creator != null ? creator.getName() : "system"); 243 return new BoxUser(Collections.unmodifiableMap(mapUser)); 244 } 245 246 /** 247 * Return a box group from a Nuxeo user metamodel 248 */ 249 @Override 250 public BoxGroup fillGroup(NuxeoGroup group) { 251 final Map<String, Object> mapGroup = new HashMap<>(); 252 mapGroup.put(BoxItem.FIELD_ID, group != null ? group.getName() : "system"); 253 mapGroup.put(BoxItem.FIELD_NAME, group != null ? group.getLabel() : "system"); 254 mapGroup.put(BoxUser.FIELD_LOGIN, group != null ? group.getName() : "system"); 255 return new BoxGroup(Collections.unmodifiableMap(mapGroup)); 256 } 257 258 @Override 259 public BoxFolder getBoxFolder(String jsonBoxFolder) throws BoxJSONException { 260 return new BoxJSONParser(new BoxResourceHub()).parseIntoBoxObject(jsonBoxFolder, BoxFolder.class); 261 } 262 263 @Override 264 public BoxFile getBoxFile(String jsonBoxFile) throws BoxJSONException { 265 return new BoxJSONParser(new BoxResourceHub()).parseIntoBoxObject(jsonBoxFile, BoxFile.class); 266 } 267 268 @Override 269 public BoxComment getBoxComment(String jsonBoxComment) throws BoxJSONException { 270 return new BoxJSONParser(new BoxResourceHub()).parseIntoBoxObject(jsonBoxComment, BoxComment.class); 271 } 272 273 @Override 274 public BoxCollaboration getBoxCollaboration(String jsonBoxCollaboration) throws BoxJSONException { 275 return new BoxJSONParser(new BoxResourceHub()).parseIntoBoxObject(jsonBoxCollaboration, BoxCollaboration.class); 276 } 277 278 @Override 279 public String getJSONFromBox(BoxTypedObject boxTypedObject) throws BoxJSONException { 280 return boxTypedObject.toJSONString(new BoxJSONParser(new BoxResourceHub())); 281 } 282 283 /** 284 * @return a Box Exception Response in JSON 285 */ 286 @Override 287 public String getJSONBoxException(Exception e, int status) { 288 NXBoxJsonException boxException = new NXBoxJsonException(); 289 // Message 290 boxException.setCode(e.getMessage()); 291 // Detailed Message 292 boxException.setMessage(e.getCause() != null ? e.getCause().getMessage() : null); 293 boxException.setStatus(status); 294 ObjectMapper mapper = new ObjectMapper(); 295 String jsonExceptionResponse = StringUtils.EMPTY; 296 try { 297 jsonExceptionResponse = mapper.writeValueAsString(boxException); 298 } catch (JsonProcessingException e1) { 299 throw new BoxRestException("error when marshalling server " + "exception:", e1, 300 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); 301 } 302 return jsonExceptionResponse; 303 } 304 305 /** 306 * @return the array containing Folder Id and Collab Id 307 */ 308 @Override 309 public String[] getCollaborationArrayIds(String collaborationId) { 310 String[] collaborationIds = collaborationId.split(BoxConstants.BOX_COLLAB_DELIM); 311 if (collaborationIds.length == 0) { 312 return new String[2]; 313 } 314 return collaborationIds; 315 } 316 317 public String computeCollaborationId(String folderId, String collaborationId) { 318 return folderId.concat(BoxConstants.BOX_COLLAB_DELIM).concat(collaborationId); 319 } 320}