Package org.apache.jackrabbit.core.data

Source Code of org.apache.jackrabbit.core.data.GarbageCollector$Listener

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License.  You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jackrabbit.core.data;

import org.apache.jackrabbit.api.management.DataStoreGarbageCollector;
import org.apache.jackrabbit.api.management.MarkEventListener;
import org.apache.jackrabbit.core.id.NodeId;
import org.apache.jackrabbit.core.id.PropertyId;
import org.apache.jackrabbit.core.RepositoryImpl;
import org.apache.jackrabbit.core.SessionImpl;
import org.apache.jackrabbit.core.SessionListener;
import org.apache.jackrabbit.core.observation.SynchronousEventListener;
import org.apache.jackrabbit.core.persistence.IterablePersistenceManager;
import org.apache.jackrabbit.core.state.ItemStateException;
import org.apache.jackrabbit.core.state.NoSuchItemStateException;
import org.apache.jackrabbit.core.state.NodeState;
import org.apache.jackrabbit.core.state.PropertyState;
import org.apache.jackrabbit.core.value.InternalValue;
import org.apache.jackrabbit.spi.Name;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.jcr.InvalidItemStateException;
import javax.jcr.Item;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.PathNotFoundException;
import javax.jcr.Property;
import javax.jcr.PropertyIterator;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.UnsupportedRepositoryOperationException;
import javax.jcr.Workspace;
import javax.jcr.observation.Event;
import javax.jcr.observation.EventIterator;
import javax.jcr.observation.ObservationManager;

/**
* Garbage collector for DataStore. This implementation is iterates through all
* nodes and reads the binary properties. To detect nodes that are moved while
* the scan runs, event listeners are started. Like the well known garbage
* collection in Java, the items that are still in use are marked. Currently
* this achieved by updating the modified date of the entries. Newly added
* entries are detected because the modified date is changed when they are
* added.
* <p>
* Example code to run the data store garbage collection:
* <pre>
* JackrabbitRepositoryFactory jf = (JackrabbitRepositoryFactory) factory;
* RepositoryManager m = factory.getRepositoryManager((JackrabbitRepository) rep);
* GarbageCollector gc = m.createDataStoreGarbageCollector();
* try {
*     gc.mark();
*     gc.sweep();
* } finally {
*     gc.close();
* }
* </pre>
*/
public class GarbageCollector implements DataStoreGarbageCollector {

    /** logger instance */
    private static final Logger LOG = LoggerFactory.getLogger(GarbageCollector.class);

    private MarkEventListener callback;

    private long sleepBetweenNodes;

    private int testDelay;

    private final DataStore store;

    private long startScanTimestamp;

    private final ArrayList<Listener> listeners = new ArrayList<Listener>();

    private final IterablePersistenceManager[] pmList;

    private final Session[] sessionList;
    private SessionListener sessionListener;

    private final AtomicBoolean closed = new AtomicBoolean();

    private boolean persistenceManagerScan;

    /**
     * Create a new garbage collector.
     * This method is usually not called by the application, it is called
     * by SessionImpl.createDataStoreGarbageCollector().
     *
     * @param rep the repository
     * @param session the session that created this object (optional)
     * @param list the persistence managers
     * @param sessionList the sessions to access the workspaces
     */
    public GarbageCollector(RepositoryImpl rep, SessionImpl session, IterablePersistenceManager[] list, Session[] sessionList) {
        store = rep.getDataStore();
        this.pmList = list;
        this.persistenceManagerScan = list != null;
        this.sessionList = sessionList;

        if (session != null) {
            // Auto-close if the main session logs out
            this.sessionListener = new SessionListener() {
                public void loggedOut(SessionImpl session) {
                }
                public void loggingOut(SessionImpl session) {
                    close();
                }
            };
            session.addListener(sessionListener);
        }
    }

    public void setSleepBetweenNodes(long millis) {
        this.sleepBetweenNodes = millis;
    }

    public long getSleepBetweenNodes() {
        return sleepBetweenNodes;
    }

    /**
     * When testing the garbage collection, a delay is used instead of simulating concurrent access.
     *
     * @param testDelay the delay in milliseconds
     */
    public void setTestDelay(int testDelay) {
        this.testDelay = testDelay;
    }

    /**
     * @deprecated use setMarkEventListener().
     */
    public void setScanEventListener(ScanEventListener callback) {
        setMarkEventListener(callback);
    }

    public void setMarkEventListener(MarkEventListener callback) {
        this.callback = callback;
    }

    /**
     * @deprecated use mark().
     */
    public void scan() throws RepositoryException {
        mark();
    }

    public void mark() throws RepositoryException {
        if (store == null) {
            throw new RepositoryException("No DataStore configured.");
        }
        long now = System.currentTimeMillis();
        if (startScanTimestamp == 0) {
            startScanTimestamp = now;
            store.updateModifiedDateOnAccess(startScanTimestamp);
        }

        if (pmList == null || !persistenceManagerScan) {
            for (Session s : sessionList) {
                scanNodes(s);
            }
        } else {
            try {
                scanPersistenceManagers();
            } catch (ItemStateException e) {
                throw new RepositoryException(e);
            }
        }
    }

    private void scanNodes(Session session) throws RepositoryException {

        // add a listener to get 'new' nodes
        // actually, new nodes are not the problem, but moved nodes
        listeners.add(new Listener(session));

        // adding a link to a BLOB updates the modified date
        // reading usually doesn't, but when scanning, it does
        recurse(session.getRootNode(), sleepBetweenNodes);
    }

    public void setPersistenceManagerScan(boolean allow) {
        persistenceManagerScan = allow;
    }

    public boolean isPersistenceManagerScan() {
        return persistenceManagerScan;
    }

    /**
     * @deprecated use isPersistenceManagerScan().
     */
    public boolean getPersistenceManagerScan() {
        return isPersistenceManagerScan();
    }

    private void scanPersistenceManagers() throws RepositoryException, ItemStateException {
        for (IterablePersistenceManager pm : pmList) {
            for (NodeId id : pm.getAllNodeIds(null, 0)) {
                if (callback != null) {
                    callback.beforeScanning(null);
                }
                try {
                    NodeState state = pm.load(id);
                    Set<Name> propertyNames = state.getPropertyNames();
                    for (Name name : propertyNames) {
                        PropertyId pid = new PropertyId(id, name);
                        PropertyState ps = pm.load(pid);
                        if (ps.getType() == PropertyType.BINARY) {
                            for (InternalValue v : ps.getValues()) {
                                // getLength will update the last modified date
                                // if the persistence manager scan is running
                                v.getLength();
                            }
                        }
                    }
                } catch (NoSuchItemStateException e) {
                    // the node may have been deleted or moved in the meantime
                    // ignore it
                }
            }
        }
    }

    /**
     * Stop the observation listener if any are installed.
     */
    public void stopScan() throws RepositoryException {
        if (listeners.size() > 0) {
            for (Listener listener : listeners) {
                try {
                    listener.stop();
                } catch (Exception e) {
                    throw new RepositoryException(e);
                }
            }
            listeners.clear();
        }
    }

    /**
     * @deprecated use sweep().
     */
    public int deleteUnused() throws RepositoryException {
        return sweep();
    }

    public int sweep() throws RepositoryException {
        if (startScanTimestamp == 0) {
            throw new RepositoryException("scan must be called first");
        }
        stopScan();
        return store.deleteAllOlderThan(startScanTimestamp);
    }

    /**
     * Get the data store if one is used.
     *
     * @return the data store, or null
     */
    public DataStore getDataStore() {
        return store;
    }

    private void recurse(final Node n, long sleep) throws RepositoryException {
        if (sleep > 0) {
            try {
                Thread.sleep(sleep);
            } catch (InterruptedException e) {
                // ignore
            }
        }
        if (callback != null) {
            callback.beforeScanning(n);
        }
        try {
            for (PropertyIterator it = n.getProperties(); it.hasNext();) {
                Property p = it.nextProperty();
                try {
                    if (p.getType() == PropertyType.BINARY) {
                        if (n.hasProperty("jcr:uuid")) {
                            rememberNode(n.getProperty("jcr:uuid").getString());
                        } else {
                            rememberNode(n.getPath());
                        }
                        if (p.isMultiple()) {
                            p.getLengths();
                        } else {
                            p.getLength();
                        }
                    }
                } catch (InvalidItemStateException e) {
                    LOG.debug("Property removed concurrently - ignoring", e);
                }
            }
        } catch (InvalidItemStateException e) {
            LOG.debug("Node removed concurrently - ignoring", e);
        }
        try {
            for (NodeIterator it = n.getNodes(); it.hasNext();) {
                recurse(it.nextNode(), sleep);
            }
        } catch (InvalidItemStateException e) {
            LOG.debug("Node removed concurrently - ignoring", e);
        }
    }

    private void rememberNode(String path) {
        // Do nothing at the moment
        // TODO It may be possible to delete some items early
        /*
         * To delete files early in the garbage collection scan, we could do
         * this:
         *
         * A) If garbage collection was run before, see if there a file with the
         * list of UUIDs ('uuids.txt').
         *
         * B) If yes, and if the checksum is ok, read all those nodes first (if
         * not so many). This updates the modified date of all old files that
         * are still in use. Afterwards, delete all files with an older modified
         * date than the last scan! Newer files, and files that are read have a
         * newer modification date.
         *
         * C) Delete the 'uuids.txt' file (in any case).
         *
         * D) Iterate (recurse) through all nodes and properties like now. If a
         * node has a binary property, store the UUID of the node in the file
         * ('uuids.txt'). Also store the time when the scan started.
         *
         * E) Checksum and close the file.
         *
         * F) Like now, delete files with an older modification date than this
         * scan.
         *
         * We can't use node path for this, UUIDs are required as nodes could be
         * moved around.
         *
         * This mechanism requires that all data stores update the last modified
         * date when calling addRecord and that record already exists.
         *
         */
    }

    public void close() {
        if (!closed.getAndSet(true)) {
            try {
                stopScan();
            } catch (RepositoryException e) {
                LOG.warn("An error occured when stopping the event listener", e);
            }
            for (Session s : sessionList) {
                s.logout();
            }
        }
    }

    /**
     * Auto-close in case the application didn't call it explicitly.
     */
    protected void finalize() throws Throwable {
        close();
        super.finalize();
    }

    /**
     * Event listener to detect moved nodes.
     * A SynchronousEventListener is used to make sure this method is called before the main iteration ends.
     */
    class Listener implements SynchronousEventListener {

        private final Session session;

        private final ObservationManager manager;

        private Exception lastException;

        Listener(Session session)
                throws UnsupportedRepositoryOperationException,
                RepositoryException {
            this.session = session;
            Workspace ws = session.getWorkspace();
            manager = ws.getObservationManager();
            manager.addEventListener(this, Event.NODE_ADDED, "/", true, null,
                    null, false);
        }

        void stop() throws Exception {
            if (lastException != null) {
                throw lastException;
            }
            manager.removeEventListener(this);
        }

        public void onEvent(EventIterator events) {
            if (testDelay > 0) {
                try {
                    Thread.sleep(testDelay);
                } catch (InterruptedException e) {
                    // ignore
                }
            }
            while (events.hasNext()) {
                Event event = events.nextEvent();
                try {
                    String path = event.getPath();
                    try {
                        Item item = session.getItem(path);
                        if (item.isNode()) {
                            Node n = (Node) item;
                            recurse(n, testDelay);
                        }
                    } catch (PathNotFoundException e) {
                        // ignore
                    }
                } catch (Exception e) {
                    lastException = e;
                }
            }
        }
    }

}
TOP

Related Classes of org.apache.jackrabbit.core.data.GarbageCollector$Listener

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.