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