001/*
002 * (C) Copyright 2006-2007 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: JOOoConvertPluginImpl.java 18651 2007-05-13 20:28:53Z sfermigier $
018 */
019
020package org.nuxeo.ecm.webapp.documentsLists;
021
022import java.io.Serializable;
023import java.security.MessageDigest;
024import java.security.NoSuchAlgorithmException;
025import java.util.ArrayList;
026import java.util.HashMap;
027import java.util.List;
028import java.util.Map;
029
030import org.apache.commons.logging.Log;
031import org.apache.commons.logging.LogFactory;
032import org.nuxeo.common.utils.Base64;
033import org.nuxeo.ecm.core.api.CoreSession;
034import org.nuxeo.ecm.core.api.DocumentModel;
035import org.nuxeo.ecm.core.api.DocumentModelList;
036import org.nuxeo.ecm.core.api.DocumentNotFoundException;
037import org.nuxeo.ecm.core.api.DocumentRef;
038import org.nuxeo.ecm.core.api.IdRef;
039import org.nuxeo.ecm.core.api.PathRef;
040import org.nuxeo.ecm.directory.DirectoryException;
041import org.nuxeo.ecm.directory.Session;
042import org.nuxeo.ecm.directory.api.DirectoryService;
043import org.nuxeo.ecm.platform.ui.web.directory.DirectoryHelper;
044
045/**
046 * Manage DocumentsLists Persistence. Uses a SQL Directory as storage Backend.
047 *
048 * @author tiry
049 */
050public class DocumentsListsPersistenceManager {
051
052    private static final String DIR_NAME = "documentsLists";
053
054    private static final String ID_SEP = ":";
055
056    private static final String DIR_COL_USERID = "userid";
057
058    private static final String DIR_COL_LISTID = "listid";
059
060    private static final String DIR_COL_REF = "ref";
061
062    private static final String DIR_COL_REFTYPE = "reftype";
063
064    private static final String DIR_COL_REPO = "repo";
065
066    // Hey, you never know !
067    private static final boolean ENABLE_SANITY_CHECK = true;
068
069    private static final boolean FIX_SANITY_ERROR = true;
070
071    private static final Log log = LogFactory.getLog(DocumentsListsPersistenceManager.class);
072
073    private DirectoryService directoryService;
074
075    private Session dirSession;
076
077    private String directorySchema;
078
079    private static String getIdForEntry(String userName, String listName, DocumentModel doc) {
080        String ref = doc.getRef().toString();
081        int refType = doc.getRef().type();
082        String repoId = doc.getRepositoryName();
083
084        return getIdForEntry(userName, listName, ref, refType, repoId);
085    }
086
087    private static String getIdForEntry(String userName, String listName, String ref, int refType, String repoId) {
088        StringBuilder sb = new StringBuilder();
089        sb.append(listName);
090        sb.append(ID_SEP);
091        sb.append(userName);
092        sb.append(ID_SEP);
093        sb.append(refType);
094        sb.append(ID_SEP);
095        sb.append(ref);
096        sb.append(ID_SEP);
097        sb.append(repoId);
098
099        byte[] idDigest;
100        try {
101            idDigest = MessageDigest.getInstance("MD5").digest(sb.toString().getBytes());
102        } catch (NoSuchAlgorithmException e) {
103            // should never append
104            return sb.toString();
105        }
106        return Base64.encodeBytes(idDigest);
107    }
108
109    private boolean initPersistentService() {
110        if (dirSession != null) {
111            return true;
112        }
113
114        if (directoryService == null) {
115            directoryService = DirectoryHelper.getDirectoryService();
116            if (directoryService == null) {
117                return false;
118            }
119        }
120
121        try {
122            dirSession = directoryService.open(DIR_NAME);
123            directorySchema = directoryService.getDirectorySchema(DIR_NAME);
124        } catch (DirectoryException e) {
125            dirSession = null;
126            log.error("Unable to open directory " + DIR_NAME + " : " + e.getMessage());
127            return false;
128        }
129        return true;
130    }
131
132    private void releasePersistenceService() {
133        // for now directory sessions are lost during passivation of the DirectoryFacade
134        // this can't be tested on the client side
135        // => release directorySession after each call ...
136
137        if (directoryService == null) {
138            dirSession = null;
139            return;
140        }
141        if (dirSession != null) {
142            try {
143                dirSession.close();
144            } catch (DirectoryException e) {
145                // do nothing
146            }
147        }
148        dirSession = null;
149    }
150
151    private static DocumentModel getDocModel(CoreSession session, String ref, long refType, String repoId) {
152
153        if (!session.getRepositoryName().equals(repoId)) {
154            log.error("Multiple repository management is not handled in current implementation");
155            return null;
156        }
157        DocumentRef docRef;
158        if (refType == DocumentRef.ID) {
159            docRef = new IdRef(ref);
160        } else if (refType == DocumentRef.PATH) {
161            docRef = new PathRef(ref);
162        } else {
163            log.error("Unknown reference type");
164            return null;
165        }
166
167        DocumentModel doc = null;
168        try {
169            doc = session.getDocument(docRef);
170        } catch (DocumentNotFoundException e) {
171            log.warn("document with ref " + ref + " was not found : " + e.getMessage());
172            return null;
173        }
174
175        return doc;
176    }
177
178    public List<DocumentModel> loadPersistentDocumentsLists(CoreSession currentSession, String userName, String listName) {
179        List<DocumentModel> docList = new ArrayList<DocumentModel>();
180        if (!initPersistentService()) {
181            return docList;
182        }
183        try {
184            Map<String, Serializable> filter = new HashMap<String, Serializable>();
185            filter.put(DIR_COL_LISTID, listName);
186            filter.put(DIR_COL_USERID, userName);
187
188            DocumentModelList entries;
189            try {
190                entries = dirSession.query(filter);
191            } catch (DirectoryException e) {
192                log.error(e, e);
193                return docList;
194            }
195
196            for (DocumentModel entry : entries) {
197                String ref = (String) entry.getProperty(directorySchema, DIR_COL_REF);
198                long reftype = (Long) entry.getProperty(directorySchema, DIR_COL_REFTYPE);
199                String repo = (String) entry.getProperty(directorySchema, DIR_COL_REPO);
200
201                DocumentModel doc = getDocModel(currentSession, ref, reftype, repo);
202
203                if (doc != null) {
204                    if (ENABLE_SANITY_CHECK) {
205                        if (docList.contains(doc)) {
206                            log.warn("Document " + doc.getRef().toString() + " is duplicated in persistent list "
207                                    + listName);
208                            if (FIX_SANITY_ERROR) {
209                                try {
210                                    dirSession.deleteEntry(entry.getId());
211                                } catch (DirectoryException e) {
212                                    log.error("Sanity fix failed", e);
213                                }
214                            }
215                        } else {
216                            docList.add(doc);
217                        }
218                    } else {
219                        docList.add(doc);
220                    }
221                } else {
222                    // not found => do the remove
223                    try {
224                        dirSession.deleteEntry(entry.getId());
225                    } catch (DirectoryException e) {
226                        log.error("Unable to remove non existing document model entry : " + entry.getId(), e);
227                    }
228                }
229            }
230            return docList;
231        } finally {
232            releasePersistenceService();
233        }
234    }
235
236    public Boolean addDocumentToPersistentList(String userName, String listName, DocumentModel doc) {
237        if (!initPersistentService()) {
238            return false;
239        }
240        try {
241            Map<String, Object> fields = new HashMap<String, Object>();
242            fields.put(DIR_COL_LISTID, listName);
243            fields.put(DIR_COL_USERID, userName);
244            fields.put(DIR_COL_REF, doc.getRef().toString());
245            fields.put(DIR_COL_REFTYPE, (long) doc.getRef().type());
246            fields.put(DIR_COL_REPO, doc.getRepositoryName());
247            String id = getIdForEntry(userName, listName, doc);
248            fields.put("id", id);
249            try {
250                if (ENABLE_SANITY_CHECK) {
251                    DocumentModel badEntry = dirSession.getEntry(id);
252                    if (badEntry != null) {
253                        log.warn("Entry with id " + id + " is already present : please check DB integrity");
254                        if (FIX_SANITY_ERROR) {
255                            dirSession.deleteEntry(id);
256                        }
257                    }
258                }
259                dirSession.createEntry(fields);
260            } catch (DirectoryException e) {
261                log.error("Unable to create entry", e);
262                return false;
263            }
264            return true;
265        } finally {
266            releasePersistenceService();
267        }
268    }
269
270    public Boolean removeDocumentFromPersistentList(String userName, String listName, DocumentModel doc) {
271        if (!initPersistentService()) {
272            return false;
273        }
274        try {
275            String entryId = getIdForEntry(userName, listName, doc);
276            try {
277                dirSession.deleteEntry(entryId);
278            } catch (DirectoryException e) {
279                log.error("Unable to delete entry", e);
280                return false;
281            }
282            return true;
283        } finally {
284            releasePersistenceService();
285        }
286    }
287
288    public Boolean clearPersistentList(String userName, String listName) {
289        if (!initPersistentService()) {
290            return false;
291        }
292        try {
293            Map<String, Serializable> filter = new HashMap<String, Serializable>();
294            filter.put(DIR_COL_LISTID, listName);
295            filter.put(DIR_COL_USERID, userName);
296            try {
297                DocumentModelList entriesToDelete = dirSession.query(filter);
298                for (DocumentModel entry : entriesToDelete) {
299                    dirSession.deleteEntry(entry.getId());
300                }
301            } catch (DirectoryException e) {
302                log.error("Unable to clear DocumentList", e);
303                return false;
304            }
305            return true;
306        } finally {
307            releasePersistenceService();
308        }
309    }
310
311}