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