001/* 002* Copyright 2016 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.workspaces.repository.maintenance; 017 018import java.sql.SQLException; 019import java.util.Iterator; 020import java.util.List; 021 022import javax.jcr.Node; 023import javax.jcr.RepositoryException; 024import javax.jcr.Session; 025import javax.jcr.SimpleCredentials; 026import javax.sql.DataSource; 027 028import org.apache.commons.io.FileUtils; 029import org.apache.jackrabbit.api.management.MarkEventListener; 030import org.apache.jackrabbit.core.RepositoryContext; 031import org.apache.jackrabbit.core.data.DataIdentifier; 032import org.apache.jackrabbit.core.data.DataStore; 033import org.apache.jackrabbit.core.data.DataStoreException; 034import org.apache.jackrabbit.core.data.FileDataStore; 035import org.apache.jackrabbit.core.data.db.DbDataStore; 036import org.apache.jackrabbit.core.data.db.DerbyDataStore; 037import org.apache.jackrabbit.core.gc.GarbageCollector; 038import org.apache.jackrabbit.core.persistence.IterablePersistenceManager; 039import org.apache.jackrabbit.core.util.db.ConnectionFactory; 040import org.apache.jackrabbit.core.util.db.ConnectionHelper; 041import org.slf4j.Logger; 042import org.slf4j.LoggerFactory; 043 044/** 045 * DataStoreGarbageCollectorTask 046 */ 047public class DataStoreGarbageCollectorTask extends AbstractMaintenanceTask implements MarkEventListener 048{ 049 private static final int SYSTEM_GC_CALLS = 3; 050 051 /** The JCR Session bound to this task. */ 052 protected Session _session; 053 054 private GarbageCollector _garbageCollector; 055 private List<IterablePersistenceManager> _pmList; 056 057 /** Internal counter for scanned node by the GC */ 058 private int _scannedNodesCount; 059 060 @Override 061 protected void initialize() throws RepositoryException 062 { 063 // Create the repository and log in the session. 064 RepositoryContext repositoryContext = createRepositoryContext(); 065 _session = repositoryContext.getRepository().login(new SimpleCredentials("__MAINTENANCE_TASK__", "".toCharArray())); 066 067 // Create the GC Object 068 this._garbageCollector = repositoryContext.getRepository().createDataStoreGarbageCollector(); 069 070 _pmList = getAllPersistenceManager(repositoryContext); 071 072 // Initialize the task progress object. 073 int count = 0; // number of item that will be scanned. 074 075 try 076 { 077 for (IterablePersistenceManager pm : _pmList) 078 { 079 count += pm.getAllNodeIds(null, 0).size(); 080 } 081 082 // When scan is finished, progress is set at 70%. 083 float total = (10f / 7) * count; 084 085 _progress = new TaskProgress(Math.max(total, 1)); 086 } 087 catch (Exception e) 088 { 089 _progress = new TaskProgress(0); 090 _progress.setInErrorState(e); 091 _logger.error(e.getLocalizedMessage(), e); 092 } 093 } 094 095 @Override 096 protected void setLogger() 097 { 098 setLogger(LoggerFactory.getLogger(DataStoreGarbageCollectorTask.class)); 099 } 100 101 @Override 102 protected void apply() throws RepositoryException 103 { 104 // call System.gc() a few times before running the data store garbage collection. 105 // Please note System.gc() does not guarantee all objects are garbage collected. 106 for (int i = 0; i < SYSTEM_GC_CALLS; i++) 107 { 108 System.gc(); 109 } 110 111 // Log some information about the ds. 112 long startSize = _reportDataStoreInfo(_garbageCollector.getDataStore()); 113 114 // Sleep 115 if (_garbageCollector.getDataStore() instanceof FileDataStore) 116 { 117 // make sure the file is old (access time resolution is 2 seconds) 118 try 119 { 120 Thread.sleep(2000); 121 } 122 catch (InterruptedException e) 123 { 124 _logger.error(e.getLocalizedMessage(), e); 125 throw new RuntimeException(e); 126 } 127 } 128 129 // GC Management 130 _garbageCollector.setMarkEventListener(this); 131 _garbageCollector.setPersistenceManagerScan(true); 132 // gc.setSleepBetweenNodes(0); 133 134 // Run GC 135 try 136 { 137 _scannedNodesCount = 0; 138 _logger.info("Scanning the repository nodes..."); 139 _garbageCollector.mark(); 140 _logger.info(_scannedNodesCount + " nodes scanned."); 141 142 _logger.info("Deleting unused items... Please be patient."); 143 int deleted = _garbageCollector.sweep(); 144 _logger.info(deleted + " unused items deleted."); 145 if (_progress != null) 146 { 147 _progress.progressRelativePercentage(50); 148 } 149 150 _logger.info("Finalizing the process..."); 151 152 // Compressing derby DATASTORE table. 153 if (_garbageCollector.getDataStore() instanceof DerbyDataStore) 154 { 155 _logger.info("Reclaiming unused space, this may take several minutes depending on the size of the data store."); 156 _logger.info("Please be patient."); 157 158 DbDataStore ds = (DbDataStore) _garbageCollector.getDataStore(); 159 DataSource dataSource = _getDataSource(ds); 160 String table = ds.getTablePrefix() + ds.getSchemaObjectPrefix() + "DATASTORE"; 161 derbyCompressTable(dataSource, table, _logger); 162 } 163 } 164 finally 165 { 166 _garbageCollector.close(); 167 } 168 169 // Log some information about the ds. 170 long finalSize = _reportDataStoreInfo(_garbageCollector.getDataStore()); 171 long freedSize = startSize - finalSize; 172 _logger.info("Size of cleared data: " + FileUtils.byteCountToDisplaySize(freedSize) + "Ko"); 173 _logger.info("The total released space on your disk can be different depending on the type of the data store used by your repository."); 174 } 175 176 @Override 177 protected void close() 178 { 179 if (_session != null) 180 { 181 _session.logout(); 182 } 183 184 super.close(); 185 186 if (_progress != null) 187 { 188 _progress.progressRelativePercentage(100); 189 } 190 } 191 192 private long _reportDataStoreInfo(DataStore ds) throws DataStoreException 193 { 194 long count = 0; 195 long total = 0; 196 Iterator<DataIdentifier> it = ds.getAllIdentifiers(); 197 while (it.hasNext()) 198 { 199 count++; 200 DataIdentifier id = it.next(); 201 total += ds.getRecord(id).getLength(); 202 } 203 204 StringBuilder sb = new StringBuilder(); 205 sb.append("Datastore item count: ").append(count).append(" "); 206 sb.append("[total size: ").append(FileUtils.byteCountToDisplaySize(total)).append("]"); 207 _logger.info(sb.toString()); 208 209 return total; 210 } 211 212 /** 213 * Retrieve the data source of a data store. 214 * @param ds the data store 215 * @return the data source 216 * @throws RepositoryException when an error occurred 217 */ 218 protected DataSource _getDataSource(DbDataStore ds) throws RepositoryException 219 { 220 DataSource dataSource = null; 221 222 try 223 { 224 ConnectionFactory cf = getRepositoryConfig().getConnectionFactory(); 225 if (ds.getDataSourceName() == null || "".equals(ds.getDataSourceName())) 226 { 227 dataSource = cf.getDataSource(ds.getDriver(), ds.getUrl(), ds.getUser(), ds.getPassword()); 228 } 229 else 230 { 231 dataSource = cf.getDataSource(ds.getDataSourceName()); 232 } 233 } 234 catch (SQLException e) 235 { 236 _logger.error(e.getLocalizedMessage(), e); 237 throw new RuntimeException(e); 238 } 239 catch (RepositoryException e) 240 { 241 _logger.warn("Failed to retrieve the data source from data store " + ds.getUrl(), e); 242 throw e; 243 } 244 return dataSource; 245 } 246 247 /** 248 * Reclaiming unused space. This is derby specific. 249 * By default, Derby does not return unused space to the operating system 250 * when updating or deleting data. 251 * @param dataSource the data source to compress 252 * @param table the table to compress 253 * @param logger the logger to use for error 254 */ 255 public static void derbyCompressTable(DataSource dataSource, String table, Logger logger) 256 { 257 ConnectionHelper conHelper = new ConnectionHelper(dataSource, false); 258 String sql = "CALL SYSCS_UTIL.SYSCS_COMPRESS_TABLE(CURRENT SCHEMA, ?, 0)"; 259 260 try 261 { 262 conHelper.query(sql, table); 263 } 264 catch (SQLException e) 265 { 266 logger.error(e.getLocalizedMessage(), e); 267 throw new RuntimeException(e); 268 } 269 } 270 271 @Override 272 public void beforeScanning(Node n) throws RepositoryException 273 { 274 _scannedNodesCount++; 275 if (_progress != null) 276 { 277 _progress.progress(); 278 } 279 } 280}