org.hippoecm.frontend.model.JcrItemModel.java Source code

Java tutorial

Introduction

Here is the source code for org.hippoecm.frontend.model.JcrItemModel.java

Source

/*
 *  Copyright 2008-2013 Hippo B.V. (http://www.onehippo.com)
 * 
 *  Licensed 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.hippoecm.frontend.model;

import java.io.IOException;
import java.io.ObjectOutputStream;

import javax.jcr.InvalidItemStateException;
import javax.jcr.Item;
import javax.jcr.ItemNotFoundException;
import javax.jcr.Node;
import javax.jcr.PathNotFoundException;
import javax.jcr.RepositoryException;
import javax.jcr.Session;

import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.apache.wicket.Application;
import org.apache.wicket.RuntimeConfigurationType;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
import org.apache.wicket.util.string.PrependingStringBuffer;
import org.hippoecm.frontend.session.UserSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Model for JCR {@link Item}s.  The model tracks the Item as well as it can, using the
 * first referenceable ancestor plus a relative path as the identification/retrieval method.
 * When the Item (or one of its ancestors) is moved, this is transparent.
 * <p>
 * In development, when the model is serialized, it checks whether it has been detached properly.
 */
public class JcrItemModel<T extends Item> extends LoadableDetachableModel<T> {

    private static final long serialVersionUID = 1L;

    static final Logger log = LoggerFactory.getLogger(JcrItemModel.class);

    // the leading id of the item is the (uuid,relPath) tuple.
    private String uuid;
    private String relPath;
    private int hash;
    private boolean property;
    private String userId;

    // the path of the item, used to retrieve the item when the uuid has not been
    // determined yet or the uuid cannot be resolved.
    private String absPath = null;

    // recursion detection
    private transient boolean detaching = false;

    // constructors

    public JcrItemModel(T item) {
        super(item);
        setUserId();
        relPath = null;
        uuid = null;
        if (item != null) {
            TraceMonitor.track(item);
            property = !item.isNode();
            doSave();
        }
    }

    @Deprecated
    public JcrItemModel(String path) {
        setUserId();
        absPath = path;
        try {
            final Item item = UserSession.get().getJcrSession().getItem(path);
            property = !item.isNode();
        } catch (RepositoryException e) {
            log.warn("Instantiation of item model by path failed: " + e);
        }
    }

    public JcrItemModel(String path, boolean property) {
        setUserId();
        uuid = null;
        absPath = path;
        this.property = property;
    }

    /**
     * Retrieve the identifier (UUID) of the first referencable ancestor node
     *
     * @return the UUID
     */
    public String getUuid() {
        save();
        return uuid;
    }

    /**
     * Retrieve the JCR path for the Item, relative to the first referencable ancestor
     *
     * @return the relative path
     */
    public String getRelativePath() {
        save();
        return relPath;
    }

    /**
     * The absolute JCR path for the Item.
     *
     * @return the absolute path
     */
    public String getPath() {
        checkLiveJcrSession();
        Item item = getObject();
        if (item != null) {
            try {
                absPath = item.getPath();
                return absPath;
            } catch (InvalidItemStateException e) {
                // ignore, item has been removed
                log.debug("Item " + absPath + " no longer exists", e);
            } catch (RepositoryException e) {
                log.error(e.getMessage(), e);
            }
        }
        return absPath;
    }

    /**
     * Determine whether the Item exists.  This will retrieve the Item from the repository.
     * If the Item has been loaded in this request cycle (e.g. using {@link IModel#getObject}), but has since
     * been removed, the returned information may be incorrect.
     *
     * @return true when the Item exists
     */
    public boolean exists() {
        checkLiveJcrSession();
        return getObject() != null;
    }

    /**
     * Retrieve the JcrItemModel for the parent {@link Node}.
     *
     * @return the parent JcrItemModel
     */
    public JcrItemModel<Node> getParentModel() {
        String path = getPath();
        if (path != null) {
            int idx = path.lastIndexOf('/');
            if (idx > 0) {
                String parent = path.substring(0, path.lastIndexOf('/'));
                return new JcrItemModel<Node>(parent, false);
            } else if (idx == 0) {
                if (path.equals("/")) {
                    return null;
                }
                return new JcrItemModel<Node>("/", false);
            } else {
                log.error("Unrecognised path " + path);
            }
        }
        return null;
    }

    // LoadableDetachableModel

    @SuppressWarnings("unchecked")
    @Override
    protected T load() {
        T object = loadModel();
        if (object != null) {
            TraceMonitor.track(object);
        }
        return object;
    }

    @SuppressWarnings("unchecked")
    protected T loadModel() {
        try {
            javax.jcr.Session session = UserSession.get().getJcrSession();
            if (!session.isLive()) {
                log.warn("session no longer exists");
                return null;
            }
            if (uuid != null) {
                Node node;
                try {
                    node = session.getNodeByIdentifier(uuid);
                    if (relPath == null) {
                        absPath = node.getPath();
                        return (T) node;
                    }
                    if (node.isSame(session.getRootNode())) {
                        absPath = "/" + relPath;
                    } else {
                        absPath = node.getPath() + "/" + relPath;
                    }
                    if (property) {
                        return (T) session.getProperty(absPath);
                    } else {
                        return (T) session.getNode(absPath);
                    }
                } catch (InvalidItemStateException ex) {
                    if (absPath != null) {
                        uuid = null;
                        relPath = null;
                        if (property) {
                            return (T) session.getProperty(absPath);
                        } else {
                            return (T) session.getNode(absPath);
                        }
                    } else {
                        throw ex;
                    }
                } catch (ItemNotFoundException ex) {
                    if (absPath != null) {
                        uuid = null;
                        relPath = null;
                        if (property) {
                            return (T) session.getProperty(absPath);
                        } else {
                            return (T) session.getNode(absPath);
                        }
                    } else {
                        throw ex;
                    }
                }
            } else if (absPath != null && !absPath.isEmpty()) {
                if (property) {
                    return (T) session.getProperty(absPath);
                } else {
                    return (T) session.getNode(absPath);
                }
            } else {
                log.debug("Neither path nor uuid present for item model, returning null");
            }
        } catch (ItemNotFoundException e) {
            log.info("ItemNotFoundException while loading JcrItemModel for uuid: {}", uuid);
        } catch (PathNotFoundException e) {
            log.info("PathNotFoundException while loading JcrItemModel: {}", e.getMessage());
        } catch (RepositoryException e) {
            log.warn("Failed to load JcrItemModel", e);
        }
        return null;
    }

    @Override
    public void detach() {
        if (isAttached()) {
            T object = this.getObject();
            if (object != null) {
                TraceMonitor.release(object);
            }
        }
        detaching = true;
        save();
        super.detach();
        detaching = false;
    }

    private void save() {
        if (uuid == null) {
            doSave();
        }
    }

    private void doSave() {
        if (!isValidSession()) {
            return;
        }
        try {
            relPath = null;
            Node node = null;
            PrependingStringBuffer spb = new PrependingStringBuffer();

            // if we have an item, use it to update the path
            Item item = getObject();
            if (item != null) {
                try {
                    absPath = item.getPath();
                    if (item.isNode()) {
                        node = (Node) item;
                    } else {
                        node = item.getParent();
                        spb.prepend(item.getName());
                        spb.prepend('/');
                    }
                } catch (InvalidItemStateException ex) {
                    // ignore; item doesn't exist anymore
                    super.detach();
                }
            }

            // no node was found, use path to resolve an ancestor
            if (node == null) {
                if (absPath != null) {
                    Session session = UserSession.get().getJcrSession();
                    String path = absPath;
                    while (path.lastIndexOf('/') > 0) {
                        spb.prepend(path.substring(path.lastIndexOf('/')));
                        path = path.substring(0, path.lastIndexOf('/'));
                        try {
                            node = (Node) session.getItem(path);
                            break;
                        } catch (PathNotFoundException ignored) {
                        }
                    }
                } else {
                    log.debug("Neither path nor uuid present");
                    return;
                }
            }

            while (node != null && JcrHelper.isVirtualNode(node)) {
                if (node.getIndex() > 1) {
                    spb.prepend(']');
                    spb.prepend(Integer.toString(node.getIndex()));
                    spb.prepend('[');
                }
                spb.prepend(node.getName());
                spb.prepend('/');
                node = node.getParent();
            }

            if (node != null) {
                uuid = node.getIdentifier();
                if (spb.length() > 1) {
                    relPath = spb.toString().substring(1);
                }
            }
        } catch (RepositoryException ex) {
            log.error(ex.toString());
        }
    }

    private void writeObject(ObjectOutputStream output) throws IOException {
        if (isAttached()) {
            log.warn("Undetached JcrItemModel " + getPath());
            T object = this.getObject();
            if (object != null) {
                TraceMonitor.trace(object);
            }
            if (RuntimeConfigurationType.DEPLOYMENT.equals(Application.get().getConfigurationType())) {
                detach();
            }
        }
        output.defaultWriteObject();
    }

    // override Object

    @Override
    public String toString() {
        if (!detaching) {
            boolean isAttached = isAttached();
            String string = new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE).append("path", getPath())
                    .toString();
            if (!isAttached) {
                detach();
            }
            return string;
        } else {
            return super.toString();
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public boolean equals(Object object) {
        if (!(object instanceof JcrItemModel)) {
            return false;
        }
        if (this == object) {
            return true;
        }
        JcrItemModel that = (JcrItemModel) object;

        // Two Objects that compare as equals must generate the same hash code,
        // but two Objects with the same hash code do not have to be equal.
        // this implicitly calls the save method when needed
        if (this.hashCode() != that.hashCode()) {
            return false;
        }

        if (this.uuid != null && !this.uuid.equals(that.uuid)) {
            return false;
        }

        if (this.relPath == null && that.relPath == null) {
            return true;
        } else {
            return this.relPath != null && this.relPath.equals(that.relPath);
        }
    }

    @Override
    public int hashCode() {
        if (hash == 0) {
            if (uuid == null) {
                // try to retrieve uuid
                save();
            }
            // prefer uuid over path
            if (uuid != null) {
                if (relPath == null) {
                    hash = uuid.hashCode();
                } else {
                    hash = uuid.hashCode() + relPath.hashCode();
                }
            } else {
                // no node found
                hash = -1;
            }
        }
        return hash;
    }

    private boolean isValidSession() {
        final Session session = UserSession.get().getJcrSession();
        return session.getUserID().equals(userId);
    }

    private void setUserId() {
        userId = UserSession.get().getJcrSession().getUserID();
    }

    private void checkLiveJcrSession() {
        // method below will throw runtime exception in case of non live session
        UserSession.get().getJcrSession();
    }
}