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