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.lang.reflect.InvocationTargetException;
019import java.lang.reflect.Method;
020import java.util.ArrayList;
021
022import javax.jcr.RepositoryException;
023import javax.jcr.Session;
024import javax.jcr.SimpleCredentials;
025
026import org.apache.jackrabbit.core.RepositoryContext;
027import org.apache.jackrabbit.core.persistence.IterablePersistenceManager;
028import org.apache.jackrabbit.core.persistence.PersistenceManager;
029import org.apache.jackrabbit.core.persistence.bundle.AbstractBundlePersistenceManager;
030import org.apache.jackrabbit.core.persistence.check.ConsistencyCheckListener;
031import org.apache.jackrabbit.core.persistence.check.ConsistencyReport;
032import org.apache.jackrabbit.core.persistence.check.ReportItem;
033import org.apache.jackrabbit.core.version.InternalVersionManagerImpl;
034import org.slf4j.LoggerFactory;
035
036/**
037 * ConsistencyCheckTask
038 */
039public class ConsistencyCheckTask extends AbstractMaintenanceTask implements ConsistencyCheckListener
040{
041    /** The JackRabbit RepositoryImpl Context */
042    protected RepositoryContext _repositoryContext;
043
044    /** The JCR Session bound to this task. */
045    protected Session _session;
046
047    private IterablePersistenceManager[] _pmList;
048
049    @Override
050    protected void initialize() throws RepositoryException
051    {
052        // Create the repository and log in the session.
053        _repositoryContext = RepositoryContext.create(_repositoryConfig);
054        _session = _repositoryContext.getRepository().login(new SimpleCredentials("__MAINTENANCE_TASK__", "".toCharArray()));
055
056        // Workaround to get the list of the PersistenceManager
057        ArrayList<PersistenceManager> pmList = new ArrayList<>();
058
059        // PM of version manager
060        InternalVersionManagerImpl vm = _repositoryContext.getInternalVersionManager();
061        pmList.add(vm.getPersistenceManager());
062
063        // PMs of workspaces.
064        String[] wspNames = _repositoryContext.getWorkspaceManager().getWorkspaceNames();
065        for (int i = 0; i < wspNames.length; i++)
066        {
067            pmList.add(getPM(wspNames[i]));
068        }
069
070        // Filtering on IterablePersistenceManager
071        _pmList = new IterablePersistenceManager[pmList.size()];
072        for (int i = 0; i < pmList.size(); i++)
073        {
074            PersistenceManager pm = pmList.get(i);
075            if (!(pm instanceof IterablePersistenceManager))
076            {
077                _pmList = null;
078                break;
079            }
080            _pmList[i] = (IterablePersistenceManager) pm;
081        }
082
083        // Initialize the task progress object.
084        int count = 0; // number of item that will be scanned.
085
086        try
087        {
088            for (IterablePersistenceManager pm : _pmList)
089            {
090                count += pm.getAllNodeIds(null, 0).size();
091            }
092            _progress = new TaskProgress(count);
093        }
094        catch (Exception e)
095        {
096            _progress = new TaskProgress(0);
097            _progress.setInErrorState(e);
098            _logger.error(e.getLocalizedMessage(), e);
099        }
100    }
101    
102    @Override
103    protected void setLogger()
104    {
105        setLogger(LoggerFactory.getLogger(ConsistencyCheckTask.class));
106    }
107
108    @Override
109    protected void apply() throws RepositoryException
110    {
111        for (PersistenceManager pm : _pmList)
112        {
113            // Do PM consistency check
114            if (pm instanceof AbstractBundlePersistenceManager)
115            {
116                // Perform the check
117                // null -> all uuids, true -> recursive, false -> nofix, null, 
118                // lost+found -> null, this -> listener 
119                ConsistencyReport report = ((AbstractBundlePersistenceManager) pm).check(null, true, false, null, this);
120                
121                _logger.info("Consistency check done for persistence manager : '" + pm.toString() + "' in " + (report.getElapsedTimeMs() / 1000f) + " s.");
122                _logger.info(report.getNodeCount() + " nodes were checked.");
123                _logger.info(report.getItems().isEmpty() ? "No consistency problems were reported." : report.getItems().size() + " consistency problems were reported."); 
124            }
125        }
126    }
127
128    @Override
129    protected void close()
130    {
131        if (_session != null)
132        {
133            _session.logout();
134        }
135
136        if (_repositoryContext != null && _repositoryContext.getRepository() != null)
137        {
138            _repositoryContext.getRepository().shutdown();
139        }
140        
141        if (_progress != null)
142        {
143            _progress.progressRelativePercentage(100);
144        }
145    }
146
147    /**
148     * Retrieves JackRabbit Persistence Manager for currently opened repository. This method uses
149     * Privileged access and will fail with security exception if used in environment with enabled security manager.
150     * @param workspaceName The workspace name
151     * @return Persistence manager used by repository.
152     */
153    protected PersistenceManager getPM(String workspaceName)
154    {
155        try
156        {
157            Object workspaceInfo = findAndInvokeMethod(_repositoryContext.getRepository(), "getWorkspaceInfo", new Object[] {workspaceName});
158            return (PersistenceManager) (findAndInvokeMethod(workspaceInfo, "getPersistenceManager", null));
159        }
160        catch (Exception e)
161        {
162            throw new RuntimeException(e);
163        }
164    }
165
166    private static Object findAndInvokeMethod(Object obj, String name, Object[] parameters) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException
167    {
168        Method m = null;
169        Method[] ms = obj.getClass().getDeclaredMethods();
170        for (int i = 0; i < ms.length; i++)
171        {
172            final Method x = ms[i];
173            if (x.getName().equals(name))
174            {
175                m = x;
176                m.setAccessible(true);
177                return m.invoke(obj, parameters);
178            }
179        }
180        
181        return null;
182    }
183
184    // Listener methods 
185
186    @Override
187    public void startCheck(String id)
188    {
189        if (_progress != null)
190        {
191            _progress.progress();
192        }
193    }
194
195    @Override
196    public void report(ReportItem item)
197    {
198        _logger.warn(item.toString());
199    }
200
201    @Override
202    public void error(String id, String message)
203    {
204        _logger.error("error during the consistency check -> id : [ " + id + "]\n" + message);
205    }
206
207    @Override
208    public void info(String id, String message)
209    {
210        _logger.info("error during the consistency check -> id : [ " + id + "]\n" + message);
211    }
212}