001/* 002 * (C) Copyright 2006-2015 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 * Bogdan Stefanescu 018 * Florent Guillaume 019 * Thierry Delprat 020 */ 021package org.nuxeo.ecm.core; 022 023import java.io.Serializable; 024import java.util.ArrayList; 025import java.util.Collection; 026import java.util.HashMap; 027import java.util.HashSet; 028import java.util.LinkedHashMap; 029import java.util.List; 030import java.util.Map; 031import java.util.Map.Entry; 032import java.util.Set; 033import java.util.concurrent.atomic.AtomicLong; 034 035import org.nuxeo.ecm.core.api.CoreInstance; 036import org.nuxeo.ecm.core.api.CoreSession; 037import org.nuxeo.ecm.core.api.IdRef; 038import org.nuxeo.ecm.core.api.IterableQueryResult; 039import org.nuxeo.ecm.core.query.sql.NXQL; 040import org.nuxeo.ecm.core.repository.RepositoryService; 041import org.nuxeo.ecm.core.versioning.DefaultVersionRemovalPolicy; 042import org.nuxeo.ecm.core.versioning.OrphanVersionRemovalFilter; 043import org.nuxeo.ecm.core.versioning.VersionRemovalPolicy; 044import org.nuxeo.runtime.api.Framework; 045import org.nuxeo.runtime.model.ComponentContext; 046import org.nuxeo.runtime.model.ComponentInstance; 047import org.nuxeo.runtime.model.DefaultComponent; 048import org.nuxeo.runtime.transaction.TransactionHelper; 049 050/** 051 * Service used to register version removal policies. 052 */ 053public class CoreService extends DefaultComponent { 054 055 private static final String VERSION_REMOVAL_POLICY_XP = "versionRemovalPolicy"; 056 057 private static final String ORPHAN_VERSION_REMOVAL_FILTER_XP = "orphanVersionRemovalFilter"; 058 059 protected static final DefaultVersionRemovalPolicy DEFAULT_VERSION_REMOVAL_POLICY = new DefaultVersionRemovalPolicy(); 060 061 protected Map<CoreServicePolicyDescriptor, VersionRemovalPolicy> versionRemovalPolicies = new LinkedHashMap<>(); 062 063 protected Map<CoreServiceOrphanVersionRemovalFilterDescriptor, OrphanVersionRemovalFilter> orphanVersionRemovalFilters = new LinkedHashMap<>(); 064 065 protected ComponentContext context; 066 067 @Override 068 public void activate(ComponentContext context) { 069 this.context = context; 070 } 071 072 @Override 073 public void deactivate(ComponentContext context) { 074 this.context = null; 075 } 076 077 @Override 078 public void registerContribution(Object contrib, String point, ComponentInstance contributor) { 079 if (VERSION_REMOVAL_POLICY_XP.equals(point)) { 080 registerVersionRemovalPolicy((CoreServicePolicyDescriptor) contrib); 081 } else if (ORPHAN_VERSION_REMOVAL_FILTER_XP.equals(point)) { 082 registerOrphanVersionRemovalFilter((CoreServiceOrphanVersionRemovalFilterDescriptor) contrib); 083 } else { 084 throw new RuntimeException("Unknown extension point: " + point); 085 } 086 } 087 088 @Override 089 public void unregisterContribution(Object contrib, String point, ComponentInstance contributor) { 090 if (VERSION_REMOVAL_POLICY_XP.equals(point)) { 091 unregisterVersionRemovalPolicy((CoreServicePolicyDescriptor) contrib); 092 } else if (ORPHAN_VERSION_REMOVAL_FILTER_XP.equals(point)) { 093 unregisterOrphanVersionRemovalFilter((CoreServiceOrphanVersionRemovalFilterDescriptor) contrib); 094 } 095 } 096 097 protected void registerVersionRemovalPolicy(CoreServicePolicyDescriptor contrib) { 098 String klass = contrib.getKlass(); 099 try { 100 VersionRemovalPolicy policy = (VersionRemovalPolicy) context.getRuntimeContext() 101 .loadClass(klass) 102 .getDeclaredConstructor() 103 .newInstance(); 104 versionRemovalPolicies.put(contrib, policy); 105 } catch (ReflectiveOperationException e) { 106 throw new RuntimeException("Failed to instantiate " + VERSION_REMOVAL_POLICY_XP + ": " + klass, e); 107 } 108 } 109 110 protected void unregisterVersionRemovalPolicy(CoreServicePolicyDescriptor contrib) { 111 versionRemovalPolicies.remove(contrib); 112 } 113 114 protected void registerOrphanVersionRemovalFilter(CoreServiceOrphanVersionRemovalFilterDescriptor contrib) { 115 String klass = contrib.getKlass(); 116 try { 117 OrphanVersionRemovalFilter filter = (OrphanVersionRemovalFilter) context.getRuntimeContext() 118 .loadClass(klass) 119 .getDeclaredConstructor() 120 .newInstance(); 121 orphanVersionRemovalFilters.put(contrib, filter); 122 } catch (ReflectiveOperationException e) { 123 throw new RuntimeException("Failed to instantiate " + ORPHAN_VERSION_REMOVAL_FILTER_XP + ": " + klass, e); 124 } 125 } 126 127 protected void unregisterOrphanVersionRemovalFilter(CoreServiceOrphanVersionRemovalFilterDescriptor contrib) { 128 orphanVersionRemovalFilters.remove(contrib); 129 } 130 131 /** Gets the last version removal policy registered. */ 132 public VersionRemovalPolicy getVersionRemovalPolicy() { 133 if (versionRemovalPolicies.isEmpty()) { 134 return DEFAULT_VERSION_REMOVAL_POLICY; 135 } else { 136 VersionRemovalPolicy versionRemovalPolicy = null; 137 for (VersionRemovalPolicy policy : versionRemovalPolicies.values()) { 138 versionRemovalPolicy = policy; 139 } 140 return versionRemovalPolicy; 141 } 142 } 143 144 /** Gets all the orphan version removal filters registered. */ 145 public Collection<OrphanVersionRemovalFilter> getOrphanVersionRemovalFilters() { 146 return orphanVersionRemovalFilters.values(); 147 } 148 149 /** 150 * Removes the orphan versions. 151 * <p> 152 * A version stays referenced, and therefore is not removed, if any proxy points to a version in the version history 153 * of any live document, or in the case of tree snapshot if there is a snapshot containing a version in the version 154 * history of any live document. 155 * 156 * @param commitSize the maximum number of orphan versions to delete in one transaction 157 * @return the number of orphan versions deleted 158 * @since 9.1 159 */ 160 public long cleanupOrphanVersions(long commitSize) { 161 RepositoryService repositoryService = Framework.getService(RepositoryService.class); 162 if (repositoryService == null) { 163 // not initialized 164 return 0; 165 } 166 List<String> repositoryNames = repositoryService.getRepositoryNames(); 167 AtomicLong count = new AtomicLong(); 168 for (String repositoryName : repositoryNames) { 169 TransactionHelper.runInTransaction(() -> { 170 CoreInstance.doPrivileged(repositoryName, (CoreSession session) -> { 171 count.addAndGet(doCleanupOrphanVersions(session, commitSize)); 172 }); 173 }); 174 } 175 return count.get(); 176 } 177 178 protected long doCleanupOrphanVersions(CoreSession session, long commitSize) { 179 // compute map of version series -> list of version ids in it 180 Map<String, List<String>> versionSeriesToVersionIds = new HashMap<>(); 181 String findVersions = "SELECT " + NXQL.ECM_UUID + ", " + NXQL.ECM_VERSION_VERSIONABLEID 182 + " FROM Document WHERE " + NXQL.ECM_ISVERSION + " = 1"; 183 try (IterableQueryResult res = session.queryAndFetch(findVersions, NXQL.NXQL)) { 184 for (Map<String, Serializable> map : res) { 185 String versionSeriesId = (String) map.get(NXQL.ECM_VERSION_VERSIONABLEID); 186 String versionId = (String) map.get(NXQL.ECM_UUID); 187 versionSeriesToVersionIds.computeIfAbsent(versionSeriesId, k -> new ArrayList<>(4)).add(versionId); 188 } 189 } 190 Set<String> seriesIds = new HashSet<>(); 191 // find the live doc ids 192 String findLive = "SELECT " + NXQL.ECM_UUID + " FROM Document WHERE " + NXQL.ECM_ISPROXY + " = 0 AND " 193 + NXQL.ECM_ISVERSION + " = 0"; 194 try (IterableQueryResult res = session.queryAndFetch(findLive, NXQL.NXQL)) { 195 for (Map<String, Serializable> map : res) { 196 String id = (String) map.get(NXQL.ECM_UUID); 197 seriesIds.add(id); 198 } 199 } 200 // find the version series for proxies 201 String findProxies = "SELECT " + NXQL.ECM_PROXY_VERSIONABLEID + " FROM Document WHERE " + NXQL.ECM_ISPROXY 202 + " = 1"; 203 try (IterableQueryResult res = session.queryAndFetch(findProxies, NXQL.NXQL)) { 204 for (Map<String, Serializable> map : res) { 205 String versionSeriesId = (String) map.get(NXQL.ECM_PROXY_VERSIONABLEID); 206 seriesIds.add(versionSeriesId); 207 } 208 } 209 // all version for series ids not found from live docs or proxies can be removed 210 Set<String> ids = new HashSet<>(); 211 for (Entry<String, List<String>> en : versionSeriesToVersionIds.entrySet()) { 212 if (seriesIds.contains(en.getKey())) { 213 continue; 214 } 215 // not referenced -> remove 216 List<String> versionIds = en.getValue(); 217 ids.addAll(versionIds); 218 } 219 // new transaction as we may have spent some time in the previous queries 220 TransactionHelper.commitOrRollbackTransaction(); 221 TransactionHelper.startTransaction(); 222 // remove these ids 223 if (!ids.isEmpty()) { 224 long n = 0; 225 for (String id : ids) { 226 session.removeDocument(new IdRef(id)); 227 n++; 228 if (n >= commitSize) { 229 session.save(); 230 TransactionHelper.commitOrRollbackTransaction(); 231 TransactionHelper.startTransaction(); 232 n = 0; 233 } 234 } 235 session.save(); 236 } 237 return ids.size(); 238 } 239 240}