001/*
002 * (C) Copyright 2007-2011 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 *     Nuxeo - initial API and implementation
018 *
019 * $Id: NuxeoRemotingBean.java 13219 2007-03-03 18:43:31Z bstefanescu $
020 */
021
022package org.nuxeo.ecm.platform.ws;
023
024import java.io.IOException;
025import java.util.ArrayList;
026import java.util.Calendar;
027import java.util.HashMap;
028import java.util.List;
029import java.util.Map;
030
031import javax.jws.WebMethod;
032import javax.jws.WebParam;
033import javax.jws.WebService;
034import javax.jws.soap.SOAPBinding;
035import javax.jws.soap.SOAPBinding.Style;
036
037import org.apache.commons.codec.binary.Base64;
038import org.apache.commons.logging.Log;
039import org.apache.commons.logging.LogFactory;
040import org.nuxeo.ecm.core.api.Blob;
041import org.nuxeo.ecm.core.api.Blobs;
042import org.nuxeo.ecm.core.api.CoreSession;
043import org.nuxeo.ecm.core.api.DataModel;
044import org.nuxeo.ecm.core.api.DocumentModel;
045import org.nuxeo.ecm.core.api.DocumentModelList;
046import org.nuxeo.ecm.core.api.DocumentNotFoundException;
047import org.nuxeo.ecm.core.api.DocumentRef;
048import org.nuxeo.ecm.core.api.IdRef;
049import org.nuxeo.ecm.core.api.NuxeoException;
050import org.nuxeo.ecm.core.api.NuxeoGroup;
051import org.nuxeo.ecm.core.api.impl.DocumentModelImpl;
052import org.nuxeo.ecm.core.api.security.ACE;
053import org.nuxeo.ecm.core.api.security.ACL;
054import org.nuxeo.ecm.core.api.security.ACP;
055import org.nuxeo.ecm.core.api.security.impl.ACLImpl;
056import org.nuxeo.ecm.core.io.download.DownloadService;
057import org.nuxeo.ecm.platform.api.ws.DocumentBlob;
058import org.nuxeo.ecm.platform.api.ws.DocumentDescriptor;
059import org.nuxeo.ecm.platform.api.ws.DocumentLoader;
060import org.nuxeo.ecm.platform.api.ws.DocumentProperty;
061import org.nuxeo.ecm.platform.api.ws.DocumentSnapshot;
062import org.nuxeo.ecm.platform.api.ws.NuxeoRemoting;
063import org.nuxeo.ecm.platform.api.ws.WsACE;
064import org.nuxeo.ecm.platform.api.ws.session.WSRemotingSession;
065import org.nuxeo.ecm.platform.mimetype.MimetypeDetectionException;
066import org.nuxeo.ecm.platform.mimetype.interfaces.MimetypeRegistry;
067import org.nuxeo.runtime.api.Framework;
068
069/**
070 * Nuxeo remoting stateful session bean.
071 *
072 * @author <a href="mailto:ja@nuxeo.com">Julien Anguenot</a>
073 * @author <a href="mailto:td@nuxeo.com">Thierry Delprat</a>
074 */
075@WebService(name = "NuxeoRemotingInterface", serviceName = "NuxeoRemotingService")
076@SOAPBinding(style = Style.DOCUMENT)
077public class NuxeoRemotingBean extends AbstractNuxeoWebService implements NuxeoRemoting {
078
079    private static final long serialVersionUID = 359922583442116202L;
080
081    private static final Log log = LogFactory.getLog(NuxeoRemotingBean.class);
082
083    @Override
084    @WebMethod
085    public String getRepositoryName(@WebParam(name = "sessionId") String sid) {
086        WSRemotingSession rs = initSession(sid);
087        return rs.getRepository();
088    }
089
090    @Override
091    @WebMethod
092    public WsACE[] getDocumentACL(@WebParam(name = "sessionId") String sid, @WebParam(name = "uuid") String uuid)
093            {
094        WSRemotingSession rs = initSession(sid);
095        ACP acp = rs.getDocumentManager().getACP(new IdRef(uuid));
096        if (acp != null) {
097            ACL acl = acp.getMergedACLs("MergedACL");
098            return WsACE.wrap(acl.toArray(new ACE[acl.size()]));
099        } else {
100            return null;
101        }
102    }
103
104    @Override
105    @WebMethod
106    public DocumentSnapshot getDocumentSnapshot(@WebParam(name = "sessionId") String sid,
107            @WebParam(name = "uuid") String uuid) {
108        return getDocumentSnapshotExt(sid, uuid, false);
109    }
110
111    @Override
112    public DocumentSnapshot getDocumentSnapshotExt(@WebParam(name = "sessionId") String sid,
113            @WebParam(name = "uuid") String uuid, @WebParam(name = "useDownloadURL") boolean useDownloadUrl)
114            {
115        WSRemotingSession rs = initSession(sid);
116        DocumentModel doc = rs.getDocumentManager().getDocument(new IdRef(uuid));
117
118        DocumentProperty[] props = getDocumentNoBlobProperties(doc, rs);
119        DocumentBlob[] blobs = getDocumentBlobs(doc, rs, useDownloadUrl);
120
121        ACE[] resACP = null;
122
123        ACP acp = doc.getACP();
124        if (acp != null) {
125            ACL acl = acp.getMergedACLs("MergedACL");
126            resACP = acl.toArray(new ACE[acl.size()]);
127        }
128        DocumentSnapshot ds = new DocumentSnapshot(props, blobs, doc.getPathAsString(), WsACE.wrap(resACP));
129        return ds;
130    }
131
132    @Override
133    @WebMethod
134    public WsACE[] getDocumentLocalACL(@WebParam(name = "sessionId") String sid, @WebParam(name = "uuid") String uuid)
135            {
136        WSRemotingSession rs = initSession(sid);
137        ACP acp = rs.getDocumentManager().getACP(new IdRef(uuid));
138        if (acp != null) {
139            ACL mergedAcl = new ACLImpl("MergedACL", true);
140            for (ACL acl : acp.getACLs()) {
141                if (!ACL.INHERITED_ACL.equals(acl.getName())) {
142                    mergedAcl.addAll(acl);
143                }
144            }
145            return WsACE.wrap(mergedAcl.toArray(new ACE[mergedAcl.size()]));
146        } else {
147            return null;
148        }
149    }
150
151    @Override
152    public boolean hasPermission(@WebParam(name = "sessionId") String sid, @WebParam(name = "uuid") String uuid,
153            @WebParam(name = "permission") String permission) {
154        WSRemotingSession rs = initSession(sid);
155        CoreSession docMgr = rs.getDocumentManager();
156        DocumentModel doc = docMgr.getDocument(new IdRef(uuid));
157        if (doc == null) {
158            throw new DocumentNotFoundException("No such document: " + uuid);
159        }
160        return docMgr.hasPermission(doc.getRef(), permission);
161    }
162
163    @Override
164    @WebMethod
165    public DocumentBlob[] getDocumentBlobs(@WebParam(name = "sessionId") String sid,
166            @WebParam(name = "uuid") String uuid) {
167        return getDocumentBlobsExt(sid, uuid, false);
168    }
169
170    @Override
171    public DocumentBlob[] getDocumentBlobsExt(@WebParam(name = "sessionId") String sid,
172            @WebParam(name = "uuid") String uuid, @WebParam(name = "useDownloadUrl") boolean useDownloadUrl)
173            {
174        WSRemotingSession rs = initSession(sid);
175        DocumentModel doc = rs.getDocumentManager().getDocument(new IdRef(uuid));
176        if (doc == null) {
177            return null;
178        }
179
180        return getDocumentBlobs(doc, rs, useDownloadUrl);
181    }
182
183    protected DocumentBlob[] getDocumentBlobs(DocumentModel doc, WSRemotingSession rs, boolean useDownloadUrl)
184            {
185        List<DocumentBlob> blobs = new ArrayList<DocumentBlob>();
186        String[] schemas = doc.getSchemas();
187        for (String schema : schemas) {
188            DataModel dm = doc.getDataModel(schema);
189            Map<String, Object> map = dm.getMap();
190            for (Map.Entry<String, Object> entry : map.entrySet()) {
191                collectBlobs(doc.getId(), schema, rs, "", map, entry.getKey(), entry.getValue(), blobs, useDownloadUrl);
192            }
193        }
194        return blobs.toArray(new DocumentBlob[blobs.size()]);
195    }
196
197    @Override
198    @WebMethod
199    public String[] listUsers(@WebParam(name = "sessionId") String sid, @WebParam(name = "startIndex") int from,
200            @WebParam(name = "endIndex") int to) {
201        WSRemotingSession rs = initSession(sid);
202        List<String> userIds = rs.getUserManager().getUserIds();
203        return userIds.toArray(new String[userIds.size()]);
204    }
205
206    @Override
207    @WebMethod
208    public String[] listGroups(@WebParam(name = "sessionId") String sid, @WebParam(name = "startIndex") int from,
209            @WebParam(name = "endIndex") int to) {
210        WSRemotingSession rs = initSession(sid);
211        List<String> groupIds = rs.getUserManager().getGroupIds();
212        return groupIds.toArray(new String[groupIds.size()]);
213    }
214
215    @Override
216    @WebMethod
217    public DocumentProperty[] getDocumentProperties(@WebParam(name = "sessionId") String sid,
218            @WebParam(name = "uuid") String uuid) {
219        WSRemotingSession rs = initSession(sid);
220
221        DocumentModel doc = rs.getDocumentManager().getDocument(new IdRef(uuid));
222        List<DocumentProperty> props = new ArrayList<DocumentProperty>();
223        if (doc != null) {
224            String[] schemas = doc.getSchemas();
225            for (String schema : schemas) {
226                DataModel dm = doc.getDataModel(schema);
227                Map<String, Object> map = dm.getMap();
228                for (Map.Entry<String, Object> entry : map.entrySet()) {
229                    collectProperty("", entry.getKey(), entry.getValue(), props);
230                }
231            }
232        }
233        return props.toArray(new DocumentProperty[props.size()]);
234    }
235
236    @Override
237    @WebMethod
238    public DocumentProperty[] getDocumentNoBlobProperties(@WebParam(name = "sessionId") String sid,
239            @WebParam(name = "uuid") String uuid) {
240        WSRemotingSession rs = initSession(sid);
241
242        DocumentModel doc = rs.getDocumentManager().getDocument(new IdRef(uuid));
243        return getDocumentNoBlobProperties(doc, rs);
244    }
245
246    protected DocumentProperty[] getDocumentNoBlobProperties(DocumentModel doc, WSRemotingSession rs)
247            {
248
249        List<DocumentProperty> props = new ArrayList<DocumentProperty>();
250        if (doc != null) {
251            DocumentLoader loader = Framework.getLocalService(DocumentLoader.class);
252            loader.fillProperties(doc, props, rs);
253        }
254        return props.toArray(new DocumentProperty[props.size()]);
255    }
256
257    @Override
258    public DocumentDescriptor getCurrentVersion(@WebParam(name = "sessionId") String sid,
259            @WebParam(name = "uuid") String uuid) {
260        WSRemotingSession rs = initSession(sid);
261        DocumentModel doc = rs.getDocumentManager().getLastDocumentVersion(new IdRef(uuid));
262        if (doc != null) {
263            return new DocumentDescriptor(doc, doc.getVersionLabel());
264        }
265        return null;
266    }
267
268    @Override
269    public DocumentDescriptor getSourceDocument(@WebParam(name = "sessionId") String sid,
270            @WebParam(name = "uuid") String uid) {
271        WSRemotingSession rs = initSession(sid);
272        DocumentModel doc = rs.getDocumentManager().getDocument(new IdRef(uid));
273        String srcid = doc.getSourceId();
274        if (srcid != null && !srcid.equals(uid)) {
275            doc = rs.getDocumentManager().getSourceDocument(doc.getRef());
276        }
277        if (doc != null) {
278            return new DocumentDescriptor(doc);
279        }
280        return null;
281    }
282
283    @Override
284    public DocumentDescriptor[] getVersions(@WebParam(name = "sessionId") String sid,
285            @WebParam(name = "uuid") String uid) {
286        WSRemotingSession rs = initSession(sid);
287        List<DocumentModel> versions = rs.getDocumentManager().getVersions(new IdRef(uid));
288        if (versions == null) {
289            return null;
290        }
291        DocumentDescriptor[] docs = new DocumentDescriptor[versions.size()];
292        int i = 0;
293        for (DocumentModel version : versions) {
294            docs[i++] = new DocumentDescriptor(version, version.getVersionLabel());
295        }
296        return null;
297    }
298
299    @Override
300    @WebMethod
301    public DocumentDescriptor getRootDocument(@WebParam(name = "sessionId") String sessionId) {
302        WSRemotingSession rs = initSession(sessionId);
303        DocumentModel doc = rs.getDocumentManager().getRootDocument();
304        return doc != null ? new DocumentDescriptor(doc) : null;
305    }
306
307    @Override
308    @WebMethod
309    public DocumentDescriptor getDocument(@WebParam(name = "sessionId") String sessionId,
310            @WebParam(name = "uuid") String uuid) {
311        WSRemotingSession rs = initSession(sessionId);
312        DocumentModel doc = rs.getDocumentManager().getDocument(new IdRef(uuid));
313        return doc != null ? new DocumentDescriptor(doc) : null;
314    }
315
316    @Override
317    @WebMethod
318    public DocumentDescriptor[] getChildren(@WebParam(name = "sessionId") String sessionId,
319            @WebParam(name = "uuid") String uuid) {
320        WSRemotingSession rs = initSession(sessionId);
321        DocumentModelList docList = rs.getDocumentManager().getChildren(new IdRef(uuid));
322        DocumentDescriptor[] docs = new DocumentDescriptor[docList.size()];
323        int i = 0;
324        for (DocumentModel doc : docList) {
325            docs[i++] = new DocumentDescriptor(doc);
326        }
327        return docs;
328    }
329
330    @SuppressWarnings("unchecked")
331    protected void collectProperty(String prefix, String name, Object value, List<DocumentProperty> props)
332            {
333        final String STRINGS_LIST_SEP = ";";
334        if (value instanceof Map) {
335            Map<String, Object> map = (Map<String, Object>) value;
336            prefix = prefix + name + '/';
337            for (Map.Entry<String, Object> entry : map.entrySet()) {
338                collectProperty(prefix, entry.getKey(), entry.getValue(), props);
339            }
340        } else if (value instanceof List) {
341            prefix = prefix + name + '/';
342            List<Object> list = (List<Object>) value;
343            for (int i = 0, len = list.size(); i < len; i++) {
344                collectProperty(prefix, String.valueOf(i), list.get(i), props);
345            }
346        } else {
347            String strValue = null;
348            if (value != null) {
349                if (value instanceof Blob) {
350                    try {
351                        // strValue = ((Blob) value).getString();
352                        byte[] bytes = ((Blob) value).getByteArray();
353                        strValue = Base64.encodeBase64String(bytes);
354                    } catch (IOException e) {
355                        throw new NuxeoException("Failed to get blob property value", e);
356                    }
357                } else if (value instanceof Calendar) {
358                    strValue = ((Calendar) value).getTime().toString();
359                } else if (value instanceof String[]) {
360                    for (String each : (String[]) value) {
361                        if (strValue == null) {
362                            strValue = each;
363                        } else {
364                            strValue = strValue + STRINGS_LIST_SEP + each;
365                        }
366                    }
367                    // FIXME: this condition is always false here.
368                } else if (value instanceof List) {
369                    for (String each : (List<String>) value) {
370                        if (strValue == null) {
371                            strValue = each;
372                        } else {
373                            strValue = strValue + STRINGS_LIST_SEP + each;
374                        }
375                    }
376                } else {
377                    strValue = value.toString();
378                } // TODO: use decode method from field type?
379            }
380            props.add(new DocumentProperty(prefix + name, strValue));
381        }
382    }
383
384    @SuppressWarnings("unchecked")
385    protected void collectBlobs(String docId, String schemaName, WSRemotingSession rs, String prefix,
386            Map<String, Object> container, String name, Object value, List<DocumentBlob> blobs, boolean useDownloadUrl)
387            {
388        if (value instanceof Map) {
389            Map<String, Object> map = (Map<String, Object>) value;
390            prefix = prefix + name + '/';
391            for (Map.Entry<String, Object> entry : map.entrySet()) {
392                collectBlobs(docId, schemaName, rs, prefix, map, entry.getKey(), entry.getValue(), blobs,
393                        useDownloadUrl);
394            }
395        } else if (value instanceof List) {
396            prefix = prefix + name + '/';
397            List<Object> list = (List<Object>) value;
398            for (int i = 0, len = list.size(); i < len; i++) {
399                collectBlobs(docId, schemaName, rs, prefix, container, String.valueOf(i), list.get(i), blobs,
400                        useDownloadUrl);
401            }
402        } else if (value instanceof Blob) {
403            try {
404                Blob blob = (Blob) value;
405                String filename = (String) container.get("filename");
406                if (filename == null) {
407                    filename = prefix + name;
408                }
409
410                DocumentBlob db = null;
411                if (useDownloadUrl) {
412                    String repoName = rs.getDocumentManager().getRepositoryName();
413                    String downloadUrl = getDownloadUrl(repoName, docId, schemaName, prefix + name, filename);
414                    db = new DocumentBlob(filename, blob.getEncoding(), blob.getMimeType(), downloadUrl);
415                } else {
416                    db = new DocumentBlob(filename, blob);
417                }
418
419                // List<String> extensions =
420                // rs.mimeTypeReg.getExtensionsFromMimetypeName(blob.getMimeType());
421                // if (extensions != null) {
422                // db.setExtensions(extensions.toArray(new
423                // String[extensions.size()]));
424                // }
425                blobs.add(db);
426            } catch (IOException e) {
427                throw new NuxeoException("Failed to get document blob", e);
428            }
429        }
430    }
431
432    protected String getSchemaPrefix(String schemaName) {
433        // XXX : no API to get the prefix from the schemaName !
434        return schemaName;
435    }
436
437    protected String getDownloadUrl(String repoName, String docId, String schemaName, String xPath, String fileName) {
438        String xpath = schemaName + ':' + xPath;
439        DownloadService downloadService = Framework.getService(DownloadService.class);
440        return "/" + downloadService.getDownloadUrl(repoName, docId, xpath, fileName);
441    }
442
443    @Override
444    @WebMethod
445    public String[] getUsers(@WebParam(name = "sessionId") String sid,
446            @WebParam(name = "parentGroup") String parentGroup) {
447        if (parentGroup == null) {
448            return listUsers(sid, 0, Integer.MAX_VALUE);
449        }
450        WSRemotingSession rs = initSession(sid);
451        NuxeoGroup group = rs.getUserManager().getGroup(parentGroup);
452        if (group == null) {
453            return null;
454        }
455        List<String> users = group.getMemberUsers();
456        return users.toArray(new String[users.size()]);
457    }
458
459    @Override
460    @WebMethod
461    public String[] getGroups(@WebParam(name = "sessionId") String sid,
462            @WebParam(name = "parentGroup") String parentGroup) {
463        WSRemotingSession rs = initSession(sid);
464
465        List<String> groups;
466        if (parentGroup == null) {
467            groups = rs.getUserManager().getTopLevelGroups();
468        } else {
469            groups = rs.getUserManager().getGroupsInGroup(parentGroup);
470        }
471        return groups.toArray(new String[groups.size()]);
472    }
473
474    @Override
475    @WebMethod
476    public String getRelativePathAsString(@WebParam(name = "sessionId") String sessionId,
477            @WebParam(name = "uuid") String uuid) {
478        WSRemotingSession rs = initSession(sessionId);
479        DocumentModel doc = rs.getDocumentManager().getDocument(new IdRef(uuid));
480        if (doc == null) {
481            log.debug("Document not found for uuid=" + uuid);
482            return "";
483        } else {
484            return doc.getPathAsString();
485        }
486    }
487
488    private Map<String, Object> createDataMap(String[] propertiesArray) {
489        Map<String, Object> map = new HashMap<String, Object>();
490
491        for (int i = 0; i < propertiesArray.length; i += 2) {
492            String key = propertiesArray[i];
493            String value = propertiesArray[i + 1];
494            String[] path = key.split("\\.");
495
496            createSubMaps(map, path, value, 0);
497        }
498
499        return map;
500    }
501
502    @SuppressWarnings("unchecked")
503    private void createSubMaps(Map<String, Object> map, String[] path, String value, int depth) {
504        String key = path[depth];
505
506        if (depth == path.length - 1) {
507            map.put(key, value);
508        } else {
509            Map<String, Object> subMap = (Map<String, Object>) map.get(key);
510            if (subMap == null) {
511                subMap = new HashMap<String, Object>();
512                map.put(path[depth], subMap);
513            }
514            createSubMaps(subMap, path, value, depth + 1);
515        }
516    }
517
518    @Override
519    @SuppressWarnings("unchecked")
520    @WebMethod
521    public String uploadDocument(@WebParam(name = "sessionId") String sid,
522            @WebParam(name = "parentUuid") String parentUUID, @WebParam(name = "type") String type,
523            @WebParam(name = "properties") String[] properties) {
524        // TODO Note: This method is intented to be a general method, but now it
525        // can only be used by NuxeoCompanionForOffice
526        // In the future, a new method (which will set the properties of a
527        // document from a given map) will be probably
528        // available in org.nuxeo.ecm.core.api.impl.DocumentHelper and then this
529        // method will be made "general".
530
531        WSRemotingSession rs = initSession(sid);
532        String name = "file_" + System.currentTimeMillis();
533        CoreSession documentManager = rs.getDocumentManager();
534        DocumentRef parentRef = new IdRef(parentUUID);
535        DocumentModel document = new DocumentModelImpl(documentManager.getDocument(parentRef).getPathAsString(), name,
536                type);
537
538        document = documentManager.createDocument(document);
539
540        Map<String, Object> propertiesMap = createDataMap(properties);
541
542        Map<String, Object> fileMap = (Map<String, Object>) propertiesMap.get("file");
543        Map<String, Object> contentMap = (Map<String, Object>) fileMap.get("content");
544        Map<String, Object> dublincoreMap = (Map<String, Object>) propertiesMap.get("dublincore");
545
546        document.setProperty("dublincore", "description", dublincoreMap.get("description"));
547        document.setProperty("dublincore", "title", dublincoreMap.get("title"));
548        String filename = (String) fileMap.get("filename");
549        final byte[] contentData = Base64.decodeBase64((String) contentMap.get("data"));
550        // String contentType = (String) contentMap.get("mime-type") ;
551        Blob blob = Blobs.createBlob(contentData);
552
553        MimetypeRegistry mimeService = Framework.getService(MimetypeRegistry.class);
554
555        String mimetype;
556        try {
557            mimetype = mimeService.getMimetypeFromFilenameAndBlobWithDefault(filename, blob, "");
558        } catch (MimetypeDetectionException e) {
559            log.error(String.format("error during mimetype detection for %s: %s", filename, e.getMessage()));
560            mimetype = "";
561        }
562
563        String encoding = (String) contentMap.get("encoding");
564        blob.setEncoding(encoding);
565        blob.setMimeType(mimetype);
566        document.setProperty("file", "content", blob);
567
568        documentManager.saveDocument(document);
569        documentManager.save();
570
571        return "";
572    }
573
574}