Package org.apache.jackrabbit.core.persistence.bundle

Source Code of org.apache.jackrabbit.core.persistence.bundle.ConsistencyCheckerImpl

/*
* 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.persistence.bundle;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;

import javax.jcr.RepositoryException;

import org.apache.jackrabbit.core.id.NodeId;
import org.apache.jackrabbit.core.persistence.check.ConsistencyCheckListener;
import org.apache.jackrabbit.core.persistence.check.ConsistencyReport;
import org.apache.jackrabbit.core.persistence.check.ConsistencyReportImpl;
import org.apache.jackrabbit.core.persistence.check.ReportItem;
import org.apache.jackrabbit.core.persistence.check.ReportItemImpl;
import org.apache.jackrabbit.core.persistence.util.NodePropBundle;
import org.apache.jackrabbit.core.state.ItemStateException;
import org.apache.jackrabbit.spi.NameFactory;
import org.apache.jackrabbit.spi.commons.name.NameConstants;
import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ConsistencyCheckerImpl {

    /** the default logger */
    private static Logger log = LoggerFactory
            .getLogger(ConsistencyCheckerImpl.class);

    private final AbstractBundlePersistenceManager pm;

    private final ConsistencyCheckListener listener;

    private static final NameFactory NF = NameFactoryImpl.getInstance();

    // process 64K nodes at once
    private static int NODESATONCE = 1024 * 64;

    public ConsistencyCheckerImpl(AbstractBundlePersistenceManager pm,
            ConsistencyCheckListener listener) {
        this.pm = pm;
        this.listener = listener;
    }

    public ConsistencyReport check(String[] uuids, boolean recursive,
            boolean fix, String lostNFoundId) throws RepositoryException {
        Set<ReportItem> reports = new HashSet<ReportItem>();

        long tstart = System.currentTimeMillis();
        int total = internalCheckConsistency(uuids, recursive, fix, reports,
                lostNFoundId);
        long elapsed = System.currentTimeMillis() - tstart;

        return new ConsistencyReportImpl(total, elapsed, reports);
    }

    private int internalCheckConsistency(String[] uuids, boolean recursive,
            boolean fix, Set<ReportItem> reports, String lostNFoundId)
            throws RepositoryException {
        int count = 0;
        Collection<NodePropBundle> modifications = new ArrayList<NodePropBundle>();
        Set<NodeId> orphaned = new HashSet<NodeId>();

        NodeId lostNFound = null;
        if (fix && lostNFoundId != null) {
            // do we have a "lost+found" node?
            try {
                NodeId tmpid = new NodeId(lostNFoundId);
                NodePropBundle lfBundle = pm.loadBundle(tmpid);
                if (lfBundle == null) {
                    error(lostNFoundId,
                            "specified 'lost+found' node does not exist");
                } else if (!NameConstants.NT_UNSTRUCTURED.equals(lfBundle
                        .getNodeTypeName())) {
                    error(lostNFoundId,
                            "specified 'lost+found' node is not of type nt:unstructured");
                } else {
                    lostNFound = lfBundle.getId();
                }
            } catch (Exception ex) {
                error(lostNFoundId, "finding 'lost+found' folder", ex);
            }
        }

        if (uuids == null) {
            try {
                List<NodeId> allIds = pm.getAllNodeIds(null, NODESATONCE);

                while (!allIds.isEmpty()) {
                    NodeId lastId = null;

                    for (NodeId id : allIds) {
                        lastId = id;
                        try {
                            // parse and check bundle
                            NodePropBundle bundle = pm.loadBundle(id);
                            if (bundle == null) {
                                error(id.toString(), "No bundle found for id '"
                                        + id + "'");
                            } else {
                                checkBundleConsistency(id, bundle, fix,
                                        modifications, lostNFound, orphaned,
                                        reports);

                                count++;
                                if (count % 1000 == 0 && listener == null) {
                                    log.info(pm + ": checked " + count
                                            + " bundles...");
                                }
                            }
                        } catch (ItemStateException e) {
                            // problem already logged (loadBundle called with
                            // logDetailedErrors=true)
                        }
                    }

                    if (!allIds.isEmpty()) {
                        allIds = pm.getAllNodeIds(lastId, NODESATONCE);
                    }
                }
            } catch (ItemStateException ex) {
                throw new RepositoryException("getting nodeIds", ex);
            }
        } else {
            // check only given uuids, handle recursive flag

            // 1) convert uuid array to modifiable list
            // 2) for each uuid do
            // a) load node bundle
            // b) check bundle, store any bundle-to-be-modified in collection
            // c) if recursive, add child uuids to list of uuids

            List<NodeId> idList = new ArrayList<NodeId>(uuids.length);
            // convert uuid string array to list of UUID objects
            for (int i = 0; i < uuids.length; i++) {
                try {
                    idList.add(new NodeId(uuids[i]));
                } catch (IllegalArgumentException e) {
                    error(uuids[i],
                            "Invalid id for consistency check, skipping: '"
                                    + uuids[i] + "': " + e);
                }
            }

            // iterate over UUIDs (including ones that are newly added inside
            // the loop!)
            for (int i = 0; i < idList.size(); i++) {
                NodeId id = idList.get(i);
                try {
                    // load the node from the database
                    NodePropBundle bundle = pm.loadBundle(id);

                    if (bundle == null) {
                        if (!isVirtualNode(id)) {
                            error(id.toString(), "No bundle found for id '"
                                    + id + "'");
                        }
                    } else {
                        checkBundleConsistency(id, bundle, fix, modifications,
                                lostNFound, orphaned, reports);

                        if (recursive) {
                            for (NodePropBundle.ChildNodeEntry entry : bundle
                                    .getChildNodeEntries()) {
                                idList.add(entry.getId());
                            }
                        }

                        count++;
                        if (count % 1000 == 0 && listener == null) {
                            log.info(pm + ": checked " + count + "/"
                                    + idList.size() + " bundles...");
                        }
                    }
                } catch (ItemStateException e) {
                    // problem already logged (loadBundle called with
                    // logDetailedErrors=true)
                }
            }
        }

        // repair collected broken bundles
        if (fix && !modifications.isEmpty()) {
            info(null, pm + ": Fixing " + modifications.size()
                    + " inconsistent bundle(s)...");
            for (NodePropBundle bundle : modifications) {
                try {
                    info(bundle.getId().toString(), pm + ": Fixing bundle '"
                            + bundle.getId() + "'");
                    bundle.markOld(); // use UPDATE instead of INSERT
                    pm.storeBundle(bundle);
                    pm.evictBundle(bundle.getId());
                } catch (ItemStateException e) {
                    error(bundle.getId().toString(), pm
                            + ": Error storing fixed bundle: " + e);
                }
            }
        }

        if (fix && lostNFoundId != null && !orphaned.isEmpty()) {
            // do we have things to add to "lost+found"?
            try {
                NodePropBundle lfBundle = pm.loadBundle(lostNFound);
                if (lfBundle == null) {
                    error(lostNFoundId, "specified 'lost+found' node does not exist");
                } else if (!NameConstants.NT_UNSTRUCTURED.equals(lfBundle
                        .getNodeTypeName())) {
                    error(lostNFoundId, "specified 'lost+found' node is not of type nt:unstructered");
                } else {
                    lfBundle.markOld();
                    for (NodeId orphan : orphaned) {
                        String nodeName = orphan + "-"
                                + System.currentTimeMillis();
                        lfBundle.addChildNodeEntry(NF.create("", nodeName),
                                orphan);
                    }
                    pm.storeBundle(lfBundle);
                    pm.evictBundle(lfBundle.getId());
                }
            } catch (Exception ex) {
                error(null, "trying orphan adoption", ex);
            }
        }

        log.info(pm + ": checked " + count + " bundles.");

        return count;
    }

    /**
     * Checks a single bundle for inconsistencies, ie. inexistent child nodes
     * and inexistent parents.
     *
     * @param id
     *            node id for the bundle to check
     * @param bundle
     *            the bundle to check
     * @param fix
     *            if <code>true</code>, repair things that can be repaired
     * @param modifications
     *            if <code>fix == true</code>, collect the repaired
     *            {@linkplain NodePropBundle bundles} here
     */
    private void checkBundleConsistency(NodeId id, NodePropBundle bundle,
            boolean fix, Collection<NodePropBundle> modifications,
            NodeId lostNFoundId, Set<NodeId> orphaned, Set<ReportItem> reports) {
        // log.info(name + ": checking bundle '" + id + "'");

        // skip all virtual nodes
        if (isVirtualNode(id)) {
            return;
        }

        if (listener != null) {
            listener.startCheck(id.toString());
        }

        // look at the node's children
        Collection<NodePropBundle.ChildNodeEntry> missingChildren = new ArrayList<NodePropBundle.ChildNodeEntry>();
        for (NodePropBundle.ChildNodeEntry entry : bundle.getChildNodeEntries()) {

            // skip check for system nodes (root, system root, version storage,
            // node types)
            if (entry.getId().toString().endsWith("babecafebabe")) {
                continue;
            }

            try {
                // analyze child node bundles
                NodePropBundle child = pm.loadBundle(entry.getId());
                String message = null;
                if (child == null) {
                    message = "NodeState '" + id
                            + "' references inexistent child" + " '"
                            + entry.getName() + "' with id " + "'"
                            + entry.getId() + "'";
                    log.error(message);
                    missingChildren.add(entry);
                } else {
                    NodeId cp = child.getParentId();
                    if (cp == null) {
                        message = "ChildNode has invalid parent id: <null>";
                        log.error(message);
                    } else if (!cp.equals(id)) {
                        message = "ChildNode has invalid parent id: '" + cp
                                + "' (instead of '" + id + "')";
                        log.error(message);
                    }
                }
                if (message != null) {
                    addMessage(reports, id, message);
                }
            } catch (ItemStateException e) {
                // problem already logged (loadBundle called with
                // logDetailedErrors=true)
                addMessage(reports, id, e.getMessage());
            }
        }
        // remove child node entry (if fixing is enabled)
        if (fix && !missingChildren.isEmpty()) {
            for (NodePropBundle.ChildNodeEntry entry : missingChildren) {
                bundle.getChildNodeEntries().remove(entry);
            }
            modifications.add(bundle);
        }

        // check parent reference
        NodeId parentId = bundle.getParentId();
        try {
            // skip root nodes (that point to itself)
            if (parentId != null && !id.toString().endsWith("babecafebabe")) {
                NodePropBundle parentBundle = pm.loadBundle(parentId);

                if (parentBundle == null) {
                    String message = "NodeState '" + id
                            + "' references inexistent parent id '" + parentId
                            + "'";
                    log.error(message);
                    addMessage(reports, id, message);
                    orphaned.add(id);
                    if (lostNFoundId != null) {
                        bundle.setParentId(lostNFoundId);
                        modifications.add(bundle);
                    }
                } else {
                    boolean found = false;

                    for (NodePropBundle.ChildNodeEntry entry : parentBundle
                            .getChildNodeEntries()) {
                        if (entry.getId().equals(id)) {
                            found = true;
                            break;
                        }
                    }

                    if (!found) {
                        String message = "NodeState '" + id
                                + "' is not referenced by its parent node '"
                                + parentId + "'";
                        log.error(message);
                        addMessage(reports, id, message);

                        int l = (int) System.currentTimeMillis();
                        int r = new Random().nextInt();
                        int n = l + r;
                        String nodeName = Integer.toHexString(n);
                        parentBundle.addChildNodeEntry(
                                NF.create("{}" + nodeName), id);
                        log.info("NodeState '" + id
                                + "' adds itself to its parent node '"
                                + parentId + "' with a new name '" + nodeName
                                + "'");
                        modifications.add(parentBundle);
                    }
                }
            }
        } catch (ItemStateException e) {
            String message = "Error reading node '" + parentId
                    + "' (parent of '" + id + "'): " + e;
            log.error(message);
            addMessage(reports, id, message);
        }
    }

    /**
     * @return whether the id is for a virtual node (not needing checking)
     */
    private boolean isVirtualNode(NodeId id) {
        String s = id.toString();
        if ("cafebabe-cafe-babe-cafe-babecafebabe".equals(s)) {
            // root node isn't virtual
            return false;
        }
        else {
            // all other system nodes are
            return s.endsWith("babecafebabe");
        }
    }


    private void addMessage(Set<ReportItem> reports, NodeId id, String message) {

        if (reports != null || listener != null) {
            ReportItem ri = new ReportItemImpl(id.toString(), message);

            if (reports != null) {
                reports.add(ri);
            }
            if (listener != null) {
                listener.report(ri);
            }
        }
    }

    private void info(String id, String message) {
        if (this.listener == null) {
            String idstring = id == null ? "" : ("Node " + id + ": ");
            log.info(idstring + message);
        } else {
            listener.info(id, message);
        }
    }

    private void error(String id, String message) {
        if (this.listener == null) {
            String idstring = id == null ? "" : ("Node " + id + ": ");
            log.error(idstring + message);
        } else {
            listener.error(id, message);
        }
    }

    private void error(String id, String message, Throwable ex) {
        String idstring = id == null ? "" : ("Node " + id + ": ");
        log.error(idstring + message, ex);
        if (listener != null) {
            listener.error(id, message);
        }
    }
}
TOP

Related Classes of org.apache.jackrabbit.core.persistence.bundle.ConsistencyCheckerImpl

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.