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