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