001/* 002 * Copyright 2022 Anyware Services 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 */ 016package org.ametys.plugins.repository.maintenance; 017 018import java.util.List; 019 020import javax.jcr.Node; 021import javax.jcr.RepositoryException; 022import javax.jcr.Session; 023import javax.jcr.SimpleCredentials; 024import javax.jcr.UnsupportedRepositoryOperationException; 025 026import org.apache.jackrabbit.core.id.NodeId; 027import org.apache.jackrabbit.core.id.PropertyId; 028import org.apache.jackrabbit.core.persistence.PersistenceManager; 029import org.apache.jackrabbit.core.persistence.pool.BundleDbPersistenceManager; 030import org.apache.jackrabbit.core.state.ItemStateException; 031import org.apache.jackrabbit.core.state.NodeReferences; 032import org.slf4j.LoggerFactory; 033 034import org.ametys.workspaces.repository.maintenance.AbstractMaintenanceTask; 035 036/** 037 * Repository maintenance task that crawls every node and check that the parent references still exists 038 */ 039public class CleanReferenceTask extends AbstractMaintenanceTask 040{ 041 private static final int __BUNDLE_SIZE = 100_000; 042 long _handled; 043 int _inconsistent; 044 int _partiallyInconsistent; 045 int _cleaned; 046 private Session _session; 047 private NodeId _last; 048 049 @Override 050 public boolean requiresOffline() 051 { 052 return false; 053 } 054 055 @Override 056 protected void initialize() throws RepositoryException 057 { 058 _session = getOrCreateRepository().login(new SimpleCredentials("__MAINTENANCE_TASK__", "".toCharArray())); 059 } 060 061 @Override 062 protected void apply() throws RepositoryException 063 { 064 PersistenceManager versionPersistenceManager = getOrCreateRepositoryContext().getInternalVersionManager().getPersistenceManager(); 065 if (versionPersistenceManager instanceof BundleDbPersistenceManager bundleVersionPersistenceManager) 066 { 067 try 068 { 069 // initialized the report 070 _handled = 0; 071 _inconsistent = 0; 072 _partiallyInconsistent = 0; 073 _cleaned = 0; 074 075 _last = null; 076 // if this fail then we can't do anything so we just throw the exception 077 List<NodeId> allNodeIds = bundleVersionPersistenceManager.getAllNodeIds(_last, __BUNDLE_SIZE); 078 long total = 0L; 079 while (!allNodeIds.isEmpty()) 080 { 081 total += allNodeIds.size(); 082 083 for (NodeId id : allNodeIds) 084 { 085 _last = id; 086 try 087 { 088 if (bundleVersionPersistenceManager.existsReferencesTo(id)) 089 { 090 _cleanReferences(id, bundleVersionPersistenceManager); 091 } 092 } 093 catch (ItemStateException e) 094 { 095 _logger.warn("Failed to retrieve references to node " + id.toString() + ". The node will be skipped.", e); 096 } 097 } 098 _logger.info(String.format("%,d nodes processed...", total)); 099 // if this fail then we can't do anything so we just throw the exception 100 allNodeIds = bundleVersionPersistenceManager.getAllNodeIds(_last, __BUNDLE_SIZE); 101 } 102 103 _logger.info(String.format("The operation is over. Out of %,d nodes found, %,d had references.%n" 104 + "%,d inconsistent nodes and %,d partially inconsistent nodes were found.%n" 105 + "%,d have been removed.", total, _handled, _inconsistent, _partiallyInconsistent, _cleaned)); 106 } 107 catch (ItemStateException e) 108 { 109 _logger.error("Failed to retrieve the node ids. Can't clean the reference", e); 110 throw new RepositoryException("Failed to retrieve the node ids. Can't clean the reference", e); 111 } 112 } 113 else 114 { 115 throw new UnsupportedRepositoryOperationException("The repository doesn't support the operation"); 116 } 117 } 118 119 private void _cleanReferences(NodeId nodeId, BundleDbPersistenceManager persistenceManager) 120 { 121 try 122 { 123 NodeReferences nodeRefs = persistenceManager.loadReferencesTo(nodeId); 124 List<PropertyId> refs = nodeRefs.getReferences(); 125 final int totalRefs = refs.size(); 126 int localInconsistent = 0; 127 128 for (PropertyId ref: refs) 129 { 130 String uuid = ref.getParentId().toString(); 131 try 132 { 133 _session.getNodeByIdentifier(uuid); 134 } 135 catch (RepositoryException e) 136 { 137 _logger.debug("Inconsistent reference: " + nodeId.toString() + " <- " + uuid); 138 localInconsistent++; 139 } 140 } 141 142 if (localInconsistent > 0) 143 { 144 if (localInconsistent == totalRefs) 145 { 146 _inconsistent++; 147 try 148 { 149 Node node = _session.getNodeByIdentifier(nodeId.toString()); 150 _logger.debug("Inconsistent references to " + node.getPath() + " (" + nodeId.toString() + ") will be deleted"); 151 persistenceManager.destroy(nodeRefs); 152 _cleaned++; 153 } 154 catch (RepositoryException e) 155 { 156 _logger.warn("Failed to retrieve node " + nodeId.toString() + ". " 157 + "This node had inconsistencies. The node will be skipped and the process will continue.", e); 158 } 159 catch (ItemStateException e) 160 { 161 _logger.warn("Failed to destroy node references to inconsistent node " + nodeId.toString() + ". " 162 + "The node will be skipped and the process will continue.", e); 163 } 164 } 165 else 166 { 167 _partiallyInconsistent++; 168 _logger.info("Node " + nodeId.toString() + " is partially inconsistent. " 169 + "Nothing has been implemented to handle those case. It will be ignored by the cleaning operation."); 170 } 171 } 172 _handled++; 173 } 174 catch (ItemStateException e) 175 { 176 _logger.warn("Failed to retrieve references to node " + nodeId.toString() + ". " 177 + "The node will be skipped and the process will continue.", e); 178 } 179 } 180 181 @Override 182 protected void close() 183 { 184 _session.logout(); 185 186 super.close(); 187 } 188 189 @Override 190 protected void setLogger() 191 { 192 setLogger(LoggerFactory.getLogger(CleanReferenceTask.class)); 193 } 194} 195