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