View Javadoc

1   /*
2    *  Copyright 2011 Hippo.
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *       http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   */
16  package org.onehippo.forge.cmisreplication;
17  
18  import java.io.IOException;
19  import java.util.HashMap;
20  import java.util.LinkedList;
21  import java.util.List;
22  import java.util.Map;
23  
24  import javax.jcr.Node;
25  import javax.jcr.NodeIterator;
26  import javax.jcr.RepositoryException;
27  import javax.jcr.query.Query;
28  import javax.jcr.query.QueryManager;
29  import javax.jcr.query.QueryResult;
30  
31  import org.apache.chemistry.opencmis.client.api.CmisObject;
32  import org.apache.chemistry.opencmis.client.api.Document;
33  import org.apache.chemistry.opencmis.client.api.Folder;
34  import org.apache.chemistry.opencmis.client.api.ItemIterable;
35  import org.apache.chemistry.opencmis.client.api.OperationContext;
36  import org.apache.chemistry.opencmis.client.api.Session;
37  import org.apache.chemistry.opencmis.client.api.SessionFactory;
38  import org.apache.chemistry.opencmis.client.runtime.SessionFactoryImpl;
39  import org.apache.chemistry.opencmis.commons.SessionParameter;
40  import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;
41  import org.apache.chemistry.opencmis.commons.enums.BindingType;
42  import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException;
43  import org.apache.commons.lang.StringUtils;
44  import org.onehippo.forge.cmisreplication.util.AssetMetadata;
45  import org.onehippo.forge.cmisreplication.util.AssetUtils;
46  import org.onehippo.forge.cmisreplication.util.CmisDocumentBinary;
47  import org.onehippo.forge.cmisreplication.util.Codecs;
48  import org.slf4j.Logger;
49  import org.slf4j.LoggerFactory;
50  
51  public class CmisDocumentsReplicator {
52  
53      private static Logger log = LoggerFactory.getLogger(CmisDocumentsReplicator.class);
54  
55      private javax.jcr.Session jcrSession;
56      private CmisRepoConfig cmisRepoConfig;
57      private HippoRepoConfig hippoRepoConfig;
58      private boolean migrateCMISDocumentsToHippo;
59      private boolean deleteHippoDocumentsWhenCMISDocumentsRemoved;
60  
61      public void setJcrSession(javax.jcr.Session jcrSession) {
62          this.jcrSession = jcrSession;
63      }
64  
65      public CmisRepoConfig getCmisRepoConfig() {
66          return cmisRepoConfig;
67      }
68  
69      public void setCmisRepoConfig(CmisRepoConfig cmisRepoConfig) {
70          this.cmisRepoConfig = cmisRepoConfig;
71      }
72  
73      public HippoRepoConfig getHippoRepoConfig() {
74          return hippoRepoConfig;
75      }
76  
77      public void setHippoRepoConfig(HippoRepoConfig hippoRepoConfig) {
78          this.hippoRepoConfig = hippoRepoConfig;
79      }
80  
81      public boolean isMigrateCMISDocumentsToHippo() {
82          return migrateCMISDocumentsToHippo;
83      }
84  
85      public void setMigrateCMISDocumentsToHippo(boolean migrateCMISDocumentsToHippo) {
86          this.migrateCMISDocumentsToHippo = migrateCMISDocumentsToHippo;
87      }
88  
89      public boolean isDeleteHippoDocumentsWhenCMISDocumentsRemoved() {
90          return deleteHippoDocumentsWhenCMISDocumentsRemoved;
91      }
92  
93      public void setDeleteHippoDocumentsWhenCMISDocumentsRemoved(boolean deleteHippoDocumentsWhenCMISDocumentsRemoved) {
94          this.deleteHippoDocumentsWhenCMISDocumentsRemoved = deleteHippoDocumentsWhenCMISDocumentsRemoved;
95      }
96  
97      public void execute() {
98          log.info("Starting Cmis Documents Replicator Execution ...");
99  
100         if (migrateCMISDocumentsToHippo) {
101             try {
102                 updateCmisDocumentsToHippoRepository();
103             } catch (Exception e) {
104                 log.warn("Failed to update Cmis Documents To Hippo Repository: " + e, e);
105             }
106         }
107 
108         if (deleteHippoDocumentsWhenCMISDocumentsRemoved) {
109             try {
110                 deleteAssetsNotHavingCorrespondingCMISDocuments();
111             } catch (Exception e) {
112                 log.warn("Failed to update Repository Documents To CMIS Repository: " + e, e);
113             }
114         }
115 
116         log.info("Stopping Cmis Documents Replicator Execution ...");
117     }
118 
119     private void updateCmisDocumentsToHippoRepository() throws RepositoryException, IOException {
120         Session session = null;
121 
122         try {
123             session = createSession();
124             OperationContext operationContext = session.createOperationContext();
125             operationContext.setMaxItemsPerPage(cmisRepoConfig.getMaxItemsPerPage());
126 
127             CmisObject seed;
128 
129             if (StringUtils.isBlank(cmisRepoConfig.getRootPath())) {
130                 seed = session.getRootFolder(operationContext);
131             } else {
132                 seed = session.getObjectByPath(cmisRepoConfig.getRootPath(), operationContext);
133             }
134 
135             List<String> documentIds = new LinkedList<String>();
136             fillAllDocumentIdsFromCMISRepository(seed, documentIds);
137             log.debug("Found {} numnber of documents", documentIds.size());
138             for (String documentId : documentIds) {
139                 Document document = null;
140 
141                 try {
142                     document = (Document) session.getObject(documentId);
143                 } catch (CmisObjectNotFoundException ignore) {
144                 }
145 
146                 if (document == null) {
147                     continue;
148                 }
149 
150                 String remoteDocument = StringUtils.removeStart(StringUtils.substringBeforeLast(document.getPaths().get(0), "/"), cmisRepoConfig.getRootPath());
151 
152                 // Remove the first start
153                 remoteDocument = StringUtils.removeStart(remoteDocument, "/");
154 
155                 // Encode the name of the document
156                 String encodedAssetName = Codecs.encodeNode(document.getName());
157 
158                 String assetFolderPath = hippoRepoConfig.getRootPath();
159                 String assetPath;
160 
161                 // No folder
162                 if (StringUtils.isEmpty(remoteDocument)) {
163                     assetPath = assetFolderPath + "/" + encodedAssetName;
164                 } else {
165                     assetPath = assetFolderPath + "/" + remoteDocument + "/" + encodedAssetName;
166                 }
167 
168                 AssetMetadata metadata = AssetUtils.getAssetMetadata(jcrSession, assetPath);
169                 long documentLastModified = document.getLastModificationDate().getTimeInMillis();
170 
171                 if (metadata == null || documentLastModified > metadata.getLastModified()) {
172                     Node assetFolderNode = AssetUtils.createAssetFolders(jcrSession, assetFolderPath);
173                     CmisDocumentBinary binary = new CmisDocumentBinary(document);
174                     AssetUtils.updateAsset(jcrSession, assetFolderNode, encodedAssetName, document, binary,
175                             cmisRepoConfig.getMetadataIdsToSync());
176 
177                     binary.dispose();
178                     log.info("Updated asset on {}", assetPath);
179                 }
180             }
181         } finally {
182             if (session != null) {
183                 try {
184                     session.clear();
185                 } catch (Exception ignore) {
186                 }
187             }
188         }
189     }
190 
191     private void deleteAssetsNotHavingCorrespondingCMISDocuments() throws RepositoryException, IOException {
192         Session session = null;
193 
194         try {
195             session = createSession();
196             OperationContext operationContext = session.createOperationContext();
197             operationContext.setMaxItemsPerPage(cmisRepoConfig.getMaxItemsPerPage());
198 
199             Node seed = null;
200 
201             if (jcrSession.itemExists(hippoRepoConfig.getRootPath())) {
202                 seed = jcrSession.getNode(hippoRepoConfig.getRootPath());
203             }
204 
205             if (seed != null) {
206                 List<String> documentIds = new LinkedList<String>();
207                 fillAllDocumentIdsFromHippoRepository(seed, documentIds);
208 
209                 for (String documentId : documentIds) {
210                     Document document = null;
211 
212                     // If a CMIS document is not found by the document ID stored in a Hippo asset node,
213                     // then remove Hippo asset node because it is supposed to be removed from the source CMIS repository.
214 
215                     try {
216                         document = (Document) session.getObject(documentId);
217                     } catch (CmisObjectNotFoundException ignore) {
218                     }
219 
220                     if (document == null) {
221                         try {
222                             removeAssetByDocumentId(jcrSession, documentId);
223                             log.info("Removed asset node corresponding to " + documentId);
224                         } catch (RepositoryException re) {
225                             log.warn("Failed to remove node corresponding to " + documentId);
226                         }
227                     }
228                 }
229             }
230         } finally {
231             if (session != null) {
232                 try {
233                     session.clear();
234                 } catch (Exception ignore) {
235                 }
236             }
237         }
238     }
239 
240     private Session createSession() {
241         Map<String, String> sessionParams = new HashMap<String, String>();
242 
243         sessionParams.put(SessionParameter.USER, cmisRepoConfig.getUsername());
244         sessionParams.put(SessionParameter.PASSWORD, cmisRepoConfig.getPassword());
245         sessionParams.put(SessionParameter.BINDING_TYPE, BindingType.ATOMPUB.value());
246         sessionParams.put(SessionParameter.ATOMPUB_URL, cmisRepoConfig.getUrl());
247         sessionParams.put(SessionParameter.REPOSITORY_ID, cmisRepoConfig.getRepositoryId());
248 
249         SessionFactory factory = SessionFactoryImpl.newInstance();
250 
251         return factory.createSession(sessionParams);
252     }
253 
254     private void fillAllDocumentIdsFromCMISRepository(CmisObject seed, List<String> documentIds) {
255         String baseType = seed.getBaseType().getId();
256         if (BaseTypeId.CMIS_DOCUMENT.value().equals(baseType)) {
257             documentIds.add(seed.getId());
258         } else if (BaseTypeId.CMIS_FOLDER.value().equals(baseType)) {
259             ItemIterable<CmisObject> children = ((Folder) seed).getChildren();
260 
261             if (children.getTotalNumItems() > 0) {
262                 for (CmisObject item : children) {
263                     fillAllDocumentIdsFromCMISRepository(item, documentIds);
264                 }
265             }
266         }
267     }
268 
269     private void fillAllDocumentIdsFromHippoRepository(Node seed, List<String> documentIds) throws RepositoryException {
270         if (seed.isNodeType(CmisReplicationTypes.HIPPO_HANDLE)) {
271             if (seed.hasNode(seed.getName())) {
272                 seed = seed.getNode(seed.getName());
273             }
274         }
275 
276         if (seed.isNodeType(CmisReplicationTypes.CMIS_DOCUMENT_TYPE) && seed.hasProperty(CmisReplicationTypes.CMIS_OBJECT_ID)) {
277             documentIds.add(seed.getProperty(CmisReplicationTypes.CMIS_OBJECT_ID).getString());
278         } else if (seed.isNodeType(CmisReplicationTypes.HIPPO_ASSET_GALLERY)) {
279             for (NodeIterator nodeIt = seed.getNodes(); nodeIt.hasNext(); ) {
280                 Node child = nodeIt.nextNode();
281 
282                 if (child != null) {
283                     fillAllDocumentIdsFromHippoRepository(child, documentIds);
284                 }
285             }
286         }
287     }
288 
289     private void removeAssetByDocumentId(javax.jcr.Session jcrSession, String documentId) throws RepositoryException {
290         QueryManager queryManager = jcrSession.getWorkspace().getQueryManager();
291         String statement = "//element(*," + CmisReplicationTypes.CMIS_DOCUMENT_TYPE + ")[@" + CmisReplicationTypes.CMIS_OBJECT_ID + "='" + documentId + "']";
292         Query query = queryManager.createQuery(statement, Query.XPATH);
293         QueryResult result = query.execute();
294         boolean removed = false;
295 
296         for (NodeIterator nodeIt = result.getNodes(); nodeIt.hasNext(); ) {
297             Node node = nodeIt.nextNode();
298 
299             if (node != null) {
300                 Node parentNode = node.getParent();
301 
302                 if (parentNode.isNodeType(CmisReplicationTypes.HIPPO_HANDLE)) {
303                     parentNode.remove();
304                     removed = true;
305                 }
306             }
307         }
308 
309         if (removed) {
310             jcrSession.save();
311         }
312     }
313 }