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.io.File; 019import java.sql.SQLException; 020 021import javax.jcr.RepositoryException; 022import javax.sql.DataSource; 023 024import org.apache.commons.io.FileUtils; 025import org.apache.commons.lang.StringUtils; 026import org.apache.jackrabbit.core.RepositoryContext; 027import org.apache.jackrabbit.core.persistence.PersistenceManager; 028import org.apache.jackrabbit.core.persistence.pool.BundleDbPersistenceManager; 029import org.apache.jackrabbit.core.persistence.pool.DerbyPersistenceManager; 030import org.apache.jackrabbit.core.util.db.ConnectionFactory; 031import org.slf4j.LoggerFactory; 032 033import org.ametys.plugins.repository.provider.AmetysPersistenceManager; 034import org.ametys.workspaces.repository.maintenance.AbstractMaintenanceTask; 035import org.ametys.workspaces.repository.maintenance.DataStoreGarbageCollectorTask; 036import org.ametys.workspaces.repository.maintenance.TaskProgress; 037 038/** 039 * Maintenance Task to reclaim unused space from the Derby tables 040 */ 041public class ReclaimUnusedSpaceTask extends AbstractMaintenanceTask 042{ 043 @Override 044 protected void apply() throws RepositoryException 045 { 046 _logger.info("Reclaiming unused space, this may take several minutes depending on the size of the data store.\nPlease be patient."); 047 048 RepositoryContext repositoryContext = getOrCreateRepositoryContext(); 049 String[] workspaceNames = repositoryContext.getWorkspaceManager().getWorkspaceNames(); 050 051 _progress = new TaskProgress(workspaceNames.length + 1); 052 053 for (String workspaceName : workspaceNames) 054 { 055 try 056 { 057 _logger.info("Handling workspace " + workspaceName + "..."); 058 PersistenceManager persistenceManager = repositoryContext.getWorkspaceInfo(workspaceName).getPersistenceManager(); 059 _reclaimUnusedSpace(persistenceManager); 060 } 061 catch (Exception e) 062 { 063 _logger.warn("An error occurred while handling workspace " + workspaceName + ". The workspace will be skipped.", e); 064 } 065 _progress.progress(); 066 } 067 068 try 069 { 070 _logger.info("Handling version history..."); 071 PersistenceManager versionPersistenceManager = repositoryContext.getInternalVersionManager().getPersistenceManager(); 072 _reclaimUnusedSpace(versionPersistenceManager); 073 } 074 catch (Exception e) 075 { 076 _logger.warn("An error occurred while handling version data source. The datasource will be skipped.", e); 077 } 078 _progress.progress(); 079 } 080 081 private void _reclaimUnusedSpace(PersistenceManager persistenceManager) throws RepositoryException 082 { 083 if (persistenceManager instanceof AmetysPersistenceManager ametysPersistenceManager) 084 { 085 // the AmetysPersistenceManager is a wrapper, we want the actual PM 086 _reclaimUnusedSpace(ametysPersistenceManager.getWrappedPM()); 087 } 088 else if (persistenceManager instanceof DerbyPersistenceManager derbyPersistenceManager) 089 { 090 _reclaimUnusedSpace(derbyPersistenceManager); 091 } 092 } 093 094 private void _reclaimUnusedSpace(DerbyPersistenceManager derbyPersistenceManager) throws RepositoryException 095 { 096 String url = derbyPersistenceManager.getUrl(); 097 String path = StringUtils.substringAfter(url, "jdbc:derby:"); 098 path = StringUtils.substringBefore(path, ";"); 099 File dbDirectory = new File(path); 100 101 DataSource dataSource = _getDataSource(derbyPersistenceManager); 102 String schemaObjectPrefix = derbyPersistenceManager.getSchemaObjectPrefix(); 103 long startSize = FileUtils.sizeOfDirectory(dbDirectory); 104 105 DataStoreGarbageCollectorTask.derbyCompressTable(dataSource, schemaObjectPrefix + "REFS", _logger); 106 DataStoreGarbageCollectorTask.derbyCompressTable(dataSource, schemaObjectPrefix + "BUNDLE", _logger); 107 108 long endSize = FileUtils.sizeOfDirectory(dbDirectory); 109 110 if (startSize > endSize) 111 { 112 _logger.info(String.format("%s freed. (%s -> %s)", FileUtils.byteCountToDisplaySize(startSize - endSize), FileUtils.byteCountToDisplaySize(startSize), FileUtils.byteCountToDisplaySize(endSize))); 113 } 114 else 115 { 116 _logger.info("No space freed"); 117 } 118 } 119 120 /** 121 * Try to retrieve the data source from a persistence manager. 122 * The data source will be retrieve either by its name or using its URL 123 * @param pm the persistence manager 124 * @return the data source 125 */ 126 private DataSource _getDataSource(BundleDbPersistenceManager pm) throws RepositoryException 127 { 128 try 129 { 130 ConnectionFactory cf = getRepositoryConfig().getConnectionFactory(); 131 if (pm.getDataSourceName() == null || "".equals(pm.getDataSourceName())) 132 { 133 return cf.getDataSource(pm.getDriver(), pm.getUrl(), pm.getUser(), pm.getPassword()); 134 } 135 else 136 { 137 return cf.getDataSource(pm.getDataSourceName()); 138 } 139 } 140 catch (SQLException e) 141 { 142 _logger.error(e.getLocalizedMessage(), e); 143 throw new RuntimeException(e); 144 } 145 catch (RepositoryException e) 146 { 147 _logger.warn("Failed to retrieve the datasource from persistence manager.", e); 148 throw e; 149 } 150 151 } 152 153 @Override 154 protected void setLogger() 155 { 156 setLogger(LoggerFactory.getLogger(ReclaimUnusedSpaceTask.class)); 157 } 158}