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; 024 025import org.apache.jackrabbit.core.RepositoryContext; 026import org.apache.jackrabbit.core.id.NodeId; 027import org.apache.jackrabbit.core.id.PropertyId; 028import org.apache.jackrabbit.core.persistence.IterablePersistenceManager; 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 RepositoryContext repositoryContext = getOrCreateRepositoryContext(); 065 List<IterablePersistenceManager> pmList = getAllPersistenceManager(repositoryContext); 066 067 for (IterablePersistenceManager pm : pmList) 068 { 069 if (pm instanceof BundleDbPersistenceManager bundleDbPersistenceManager) 070 { 071 _logger.info("Cleaning nodes for persistence manager {}", pm); 072 _cleanReferences(bundleDbPersistenceManager); 073 } 074 else 075 { 076 _logger.info("The persistence manager {} doesn't support the operation. It will be skipped.", pm.getClass().getName()); 077 } 078 } 079 } 080 081 private void _cleanReferences(BundleDbPersistenceManager bundleDbPersistenceManager) throws RepositoryException 082 { 083 try 084 { 085 // initialized the report 086 _handled = 0; 087 _inconsistent = 0; 088 _partiallyInconsistent = 0; 089 _cleaned = 0; 090 091 _last = null; 092 // if this fail then we can't do anything so we just throw the exception 093 List<NodeId> allNodeIds = bundleDbPersistenceManager.getAllNodeIds(_last, __BUNDLE_SIZE); 094 long total = 0L; 095 while (!allNodeIds.isEmpty()) 096 { 097 total += allNodeIds.size(); 098 099 for (NodeId id : allNodeIds) 100 { 101 _last = id; 102 try 103 { 104 if (bundleDbPersistenceManager.existsReferencesTo(id)) 105 { 106 _cleanReferences(id, bundleDbPersistenceManager); 107 } 108 } 109 catch (ItemStateException e) 110 { 111 _logger.warn("Failed to retrieve references to node " + id.toString() + ". The node will be skipped.", e); 112 } 113 } 114 115 _logger.info(String.format("%,d nodes processed...", total)); 116 117 // if this fail then we can't do anything so we just throw the exception 118 allNodeIds = bundleDbPersistenceManager.getAllNodeIds(_last, __BUNDLE_SIZE); 119 } 120 121 _logger.info(String.format("The operation is over. Out of %,d nodes found, %,d had references.%n" 122 + "%,d inconsistent nodes and %,d partially inconsistent nodes were found.%n" 123 + "%,d have been removed.", total, _handled, _inconsistent, _partiallyInconsistent, _cleaned)); 124 } 125 catch (ItemStateException e) 126 { 127 _logger.error("Failed to retrieve the node ids. Can't clean the reference", e); 128 throw new RepositoryException("Failed to retrieve the node ids. Can't clean the reference", e); 129 } 130 } 131 132 private void _cleanReferences(NodeId nodeId, BundleDbPersistenceManager persistenceManager) 133 { 134 try 135 { 136 NodeReferences nodeRefs = persistenceManager.loadReferencesTo(nodeId); 137 List<PropertyId> refs = nodeRefs.getReferences(); 138 final int totalRefs = refs.size(); 139 int localInconsistent = 0; 140 141 for (PropertyId ref: refs) 142 { 143 String uuid = ref.getParentId().toString(); 144 try 145 { 146 _session.getNodeByIdentifier(uuid); 147 } 148 catch (RepositoryException e) 149 { 150 _logger.debug("Inconsistent reference: " + nodeId.toString() + " <- " + uuid); 151 localInconsistent++; 152 } 153 } 154 155 if (localInconsistent > 0) 156 { 157 if (localInconsistent == totalRefs) 158 { 159 _inconsistent++; 160 try 161 { 162 Node node = _session.getNodeByIdentifier(nodeId.toString()); 163 _logger.debug("Inconsistent references to " + node.getPath() + " (" + nodeId.toString() + ") will be deleted"); 164 persistenceManager.destroy(nodeRefs); 165 _cleaned++; 166 } 167 catch (RepositoryException e) 168 { 169 _logger.warn("Failed to retrieve node " + nodeId.toString() + ". " 170 + "This node had inconsistencies. The node will be skipped and the process will continue.", e); 171 } 172 catch (ItemStateException e) 173 { 174 _logger.warn("Failed to destroy node references to inconsistent node " + nodeId.toString() + ". " 175 + "The node will be skipped and the process will continue.", e); 176 } 177 } 178 else 179 { 180 _partiallyInconsistent++; 181 _logger.info("Node " + nodeId.toString() + " is partially inconsistent. " 182 + "Nothing has been implemented to handle those case. It will be ignored by the cleaning operation."); 183 } 184 } 185 _handled++; 186 } 187 catch (ItemStateException e) 188 { 189 _logger.warn("Failed to retrieve references to node " + nodeId.toString() + ". " 190 + "The node will be skipped and the process will continue.", e); 191 } 192 } 193 194 @Override 195 protected void close() 196 { 197 _session.logout(); 198 199 super.close(); 200 } 201 202 @Override 203 protected void setLogger() 204 { 205 setLogger(LoggerFactory.getLogger(CleanReferenceTask.class)); 206 } 207}