info.magnolia.ui.framework.message.MessageStore.java Source code

Java tutorial

Introduction

Here is the source code for info.magnolia.ui.framework.message.MessageStore.java

Source

/**
 * This file Copyright (c) 2012-2015 Magnolia International
 * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
 *
 *
 * This file is dual-licensed under both the Magnolia
 * Network Agreement and the GNU General Public License.
 * You may elect to use one or the other of these licenses.
 *
 * This file is distributed in the hope that it will be
 * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
 * Redistribution, except as permitted by whichever of the GPL
 * or MNA you select, is prohibited.
 *
 * 1. For the GPL license (GPL), you can redistribute and/or
 * modify this file under the terms of the GNU General
 * Public License, Version 3, as published by the Free Software
 * Foundation.  You should have received a copy of the GNU
 * General Public License, Version 3 along with this program;
 * if not, write to the Free Software Foundation, Inc., 51
 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * 2. For the Magnolia Network Agreement (MNA), this file
 * and the accompanying materials are made available under the
 * terms of the MNA which accompanies this distribution, and
 * is available at http://www.magnolia-cms.com/mna.html
 *
 * Any modifications to this file must keep this entire header
 * intact.
 *
 */
package info.magnolia.ui.framework.message;

import info.magnolia.context.MgnlContext;
import info.magnolia.jcr.node2bean.Node2BeanException;
import info.magnolia.jcr.util.NodeTypes;
import info.magnolia.jcr.util.NodeUtil;
import info.magnolia.jcr.util.PropertyUtil;
import info.magnolia.ui.api.message.Message;
import info.magnolia.ui.api.message.MessageType;
import info.magnolia.ui.framework.AdmincentralNodeTypes;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.inject.Singleton;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.query.Query;
import javax.jcr.query.QueryManager;
import javax.jcr.query.QueryResult;

import org.apache.commons.lang3.StringUtils;
import org.apache.jackrabbit.commons.JcrUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.Iterables;

/**
 * Stores messages on behalf of {@link MessagesManager} in the repository, every user in the system has its own set of
 * messages that have ids unique in combination with their userid. Ids are generated by taking the largest id in use and
 * incrementing it by 1.
 */
@Singleton
public class MessageStore {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    static final String WORKSPACE_NAME = "messages";
    static final String MESSAGE_NODE_TYPE = AdmincentralNodeTypes.SystemMessage.NAME;

    private static final String WORKSPACE_PATH = "/";
    private static final String USER_NODE_TYPE = NodeTypes.Content.NAME;

    private static final String WHERE_TYPE_CLAUSE = " [messagetype] = '%s' ";
    protected static final String ORDER_BY = " order by ";
    protected static final String ASCENDING_KEYWORD = " asc";
    protected static final String DESCENDING_KEYWORD = " desc";

    private static final String QUERY_MESSAGE_STATEMENT = "select * from [mgnl:systemMessage] where ISDESCENDANTNODE([/%s])";
    private static final String UNCLEARED_MSG_QUERY = "select * from [mgnl:systemMessage] as t where isdescendantnode([/%s]) and t.[cleared] = %s";

    /**
     * Stores a new message or overwrites an existing one depending on whether there's an id set. That is, the id of the
     * message is respected if present otherwise a new unique one is used. When the method returns the message has been
     * updated with a new id.
     *
     * @param userName user to save the message for
     * @param message message to save
     * @return true if saving was successful or false if it failed
     */
    public synchronized boolean saveMessage(final String userName, final Message message) {

        return MgnlContext.doInSystemContext(new MgnlContext.Op<Boolean, RuntimeException>() {

            @Override
            public Boolean exec() {
                try {
                    Session session = MgnlContext.getJCRSession(WORKSPACE_NAME);

                    if (message.getId() == null) {
                        message.setId(getUniqueMessageId(getOrCreateUserNode(session, userName)));
                    }

                    marshallMessage(message, getOrCreateMessageNode(session, userName, message));

                    session.save();

                    return true;

                } catch (RepositoryException e) {
                    logger.error("Saving message failed for user: " + userName, e);
                    return false;
                }
            }
        });
    }

    public synchronized int getNumberOfUnclearedMessagesForUser(final String userName) {

        return MgnlContext.doInSystemContext(new MgnlContext.Op<Integer, RuntimeException>() {
            @Override
            public Integer exec() throws RuntimeException {
                try {
                    return (int) executeQuery(String.format(UNCLEARED_MSG_QUERY, userName, false), Query.JCR_SQL2,
                            -1, -1).getNodes().getSize();
                } catch (RepositoryException e) {
                    logger.warn("Failed to find the number of uncleared messages for user: " + userName, e);
                    return 0;
                }
            }
        });
    }

    public synchronized List<Message> findAllMessagesForUser(final String userName) {
        return MgnlContext.doInSystemContext(new MgnlContext.Op<List<Message>, RuntimeException>() {

            @Override
            public List<Message> exec() throws RuntimeException {
                try {
                    Session session = MgnlContext.getJCRSession(WORKSPACE_NAME);

                    ArrayList<Message> messages = new ArrayList<Message>();

                    for (Node messageNode : NodeUtil.getNodes(getOrCreateUserNode(session, userName),
                            MESSAGE_NODE_TYPE)) {

                        Message message = unmarshallMessage(messageNode);

                        messages.add(message);
                    }
                    return messages;

                } catch (RepositoryException e) {
                    logger.error("Retrieving messages from JCR failed for user: " + userName, e);
                    return new ArrayList<Message>();
                } catch (Node2BeanException e) {
                    logger.error("Unmarshalling message failed for user: " + userName, e);
                    return new ArrayList<Message>();
                }
            }
        });
    }

    private String constructOrderBy(Map<String, Boolean> sortingCriteria) {
        StringBuilder stmt = new StringBuilder();
        final Iterator<Map.Entry<String, Boolean>> it = sortingCriteria.entrySet().iterator();
        if (it.hasNext()) {
            stmt.append(ORDER_BY);
        }

        while (it.hasNext()) {
            final Map.Entry<String, Boolean> entry = it.next();

            String propertyName = entry.getKey();
            stmt.append("[").append(propertyName).append("]")
                    .append(entry.getValue() ? ASCENDING_KEYWORD : DESCENDING_KEYWORD);

            if (it.hasNext()) {
                stmt.append(", ");
            }
        }

        return stmt.toString();
    }

    public List<Message> getMessages(final String userName, final List<MessageType> types,
            final Map<String, Boolean> sortCriteria, final int limit, final int offset) {
        return MgnlContext.doInSystemContext(new MgnlContext.Op<List<Message>, RuntimeException>() {

            @Override
            public List<Message> exec() throws RuntimeException {
                try {
                    String statement = buildQueryMessageStatement(userName, types, sortCriteria);
                    final QueryResult result = executeQuery(statement, Query.JCR_SQL2, limit, offset);

                    final List<Message> messages = new ArrayList<Message>();

                    NodeIterator nodeIterator = result.getNodes();
                    while (nodeIterator.hasNext()) {
                        Node messageNode = nodeIterator.nextNode();
                        Message message = unmarshallMessage(messageNode);

                        messages.add(message);
                    }

                    return messages;

                } catch (RepositoryException e) {
                    logger.error("Retrieving messages from JCR failed for user: " + userName, e);
                    return new ArrayList<Message>();
                } catch (Node2BeanException e) {
                    logger.error("Unmarshalling message failed for user: " + userName, e);
                    return new ArrayList<Message>();
                }
            }
        });
    }

    public long getMessageAmount(final String userName, final List<MessageType> types) {
        return MgnlContext.doInSystemContext(new MgnlContext.Op<Long, RuntimeException>() {

            @Override
            public Long exec() throws RuntimeException {
                try {
                    String statement = buildQueryMessageStatement(userName, types,
                            Collections.<String, Boolean>emptyMap());
                    QueryResult result = executeQuery(statement, Query.JCR_SQL2, 0, 0);

                    return result.getNodes().getSize();

                } catch (RepositoryException e) {
                    logger.error("Retrieving messages from JCR failed for user: " + userName, e);
                    return 0L;
                }
            }
        });
    }

    protected String buildQueryMessageStatement(String userName, List<MessageType> messageTypeList,
            Map<String, Boolean> sortCriteria) {
        MessageType[] messageTypes;
        if (Iterables.elementsEqual(messageTypeList, Arrays.asList(MessageType.values()))) {
            messageTypes = new MessageType[] {};
        } else {
            messageTypes = messageTypeList.toArray(new MessageType[messageTypeList.size()]);
        }
        return String.format(QUERY_MESSAGE_STATEMENT, userName).concat(buildWhereTypesClause(messageTypes))
                .concat(constructOrderBy(sortCriteria));
    }

    protected String buildWhereTypesClause(MessageType[] messageTypes) {
        if (messageTypes == null || messageTypes.length == 0)
            return StringUtils.EMPTY;

        StringBuilder whereClauseStatement = new StringBuilder(" and (");
        for (MessageType messageType : messageTypes) {
            whereClauseStatement.append(String.format(WHERE_TYPE_CLAUSE, messageType.name()));
            whereClauseStatement.append(" or ");
        }
        whereClauseStatement.delete(whereClauseStatement.length() - " or ".length(), whereClauseStatement.length());
        whereClauseStatement.append(")");
        return whereClauseStatement.toString();
    }

    protected QueryResult executeQuery(String statement, String language, int limit, int offset)
            throws RepositoryException {
        final Session jcrSession = MgnlContext.getJCRSession(WORKSPACE_NAME);
        final QueryManager jcrQueryManager = jcrSession.getWorkspace().getQueryManager();
        final Query query = jcrQueryManager.createQuery(statement, language);
        if (limit > 0) {
            query.setLimit(limit);
        }
        if (offset >= 0) {
            query.setOffset(offset);
        }
        logger.debug("Executing query against workspace [{}] with statement [{}] and limit {} and offset {}...",
                new Object[] { WORKSPACE_NAME, statement, limit, offset });
        long start = System.currentTimeMillis();
        final QueryResult result = query.execute();
        logger.debug("Query execution took {} ms", System.currentTimeMillis() - start);

        return result;
    }

    public synchronized Message findMessageById(final String userName, final String messageId) {

        return MgnlContext.doInSystemContext(new MgnlContext.Op<Message, RuntimeException>() {

            @Override
            public Message exec() {
                try {
                    Session session = MgnlContext.getJCRSession(WORKSPACE_NAME);

                    Node messageNode = getMessageNode(session, userName, messageId);

                    if (messageNode == null) {
                        return null;
                    }

                    return unmarshallMessage(messageNode);

                } catch (RepositoryException e) {
                    logger.error("Unable to read message: " + messageId + " for user: " + userName, e);
                    return null;
                } catch (Node2BeanException e) {
                    logger.error("Unable to read message: " + messageId + " for user: " + userName, e);
                    return null;
                }
            }
        });
    }

    public synchronized void removeMessageById(final String userName, final String messageId) {
        MgnlContext.doInSystemContext(new MgnlContext.Op<Void, RuntimeException>() {

            @Override
            public Void exec() {
                try {
                    Session session = MgnlContext.getJCRSession(WORKSPACE_NAME);

                    Node messageNode = getMessageNode(session, userName, messageId);

                    if (messageNode == null) {
                        return null;
                    }

                    messageNode.remove();

                    session.save();

                } catch (RepositoryException e) {
                    logger.error("Unable to read message: " + messageId + " for user: " + userName, e);
                }
                return null;
            }
        });

    }

    public synchronized int getNumberOfUnclearedMessagesForUserAndByType(final String userName,
            final MessageType type) {
        return MgnlContext.doInSystemContext(new MgnlContext.Op<Integer, RuntimeException>() {

            @Override
            public Integer exec() throws RuntimeException {
                try {
                    Session session = MgnlContext.getJCRSession(WORKSPACE_NAME);

                    int n = 0;
                    for (Node messageNode : NodeUtil.getNodes(getOrCreateUserNode(session, userName),
                            MESSAGE_NODE_TYPE)) {
                        if (messageNode.getProperty(AdmincentralNodeTypes.SystemMessage.MESSAGETYPE).getString()
                                .equals(type.name())
                                && !messageNode.getProperty(AdmincentralNodeTypes.SystemMessage.CLEARED)
                                        .getBoolean()) {
                            n++;
                        }
                    }
                    return n;

                } catch (RepositoryException e) {
                    logger.warn("Failed to find the number of uncleared messages for user: " + userName, e);
                    return 0;
                }
            }
        });
    }

    void marshallMessage(final Message message, final Node node) throws RepositoryException {
        node.setProperty(AdmincentralNodeTypes.SystemMessage.ID, message.getId());
        node.setProperty(AdmincentralNodeTypes.SystemMessage.TIMESTAMP, message.getTimestamp());
        node.setProperty(AdmincentralNodeTypes.SystemMessage.SENDER,
                message.getSender() != null ? message.getSender() : "");
        node.setProperty(AdmincentralNodeTypes.SystemMessage.MESSAGE,
                message.getMessage() != null ? message.getMessage() : "");
        node.setProperty(AdmincentralNodeTypes.SystemMessage.SUBJECT,
                message.getSubject() != null ? message.getSubject() : "");
        node.setProperty(AdmincentralNodeTypes.SystemMessage.MESSAGETYPE,
                message.getType() != null ? message.getType().name() : MessageType.UNKNOWN.name());
        node.setProperty(AdmincentralNodeTypes.SystemMessage.VIEW, message.getView());
        node.setProperty(AdmincentralNodeTypes.SystemMessage.CLEARED, message.isCleared());

        final Iterator<String> propertyNames = message.getPropertNames().iterator();
        while (propertyNames.hasNext()) {
            final String propertyName = propertyNames.next();
            PropertyUtil.setProperty(node, propertyName, message.getProperty(propertyName));
        }
    }

    Message unmarshallMessage(Node node) throws RepositoryException, Node2BeanException {
        Map<String, Object> map = Node2MapUtil.node2map(node);
        long timestamp = ((Long) map.get(AdmincentralNodeTypes.SystemMessage.TIMESTAMP)).longValue();

        final Message message = new Message(timestamp);
        message.setId(node.getName());
        message.setSender(node.getProperty(AdmincentralNodeTypes.SystemMessage.SENDER).getString());
        message.setMessage(node.getProperty(AdmincentralNodeTypes.SystemMessage.MESSAGE).getString());
        message.setSubject(node.getProperty(AdmincentralNodeTypes.SystemMessage.SUBJECT).getString());
        message.setType(
                MessageType.valueOf(node.getProperty(AdmincentralNodeTypes.SystemMessage.MESSAGETYPE).getString()));
        message.setCleared(node.getProperty(AdmincentralNodeTypes.SystemMessage.CLEARED).getBoolean());
        if (node.hasProperty(AdmincentralNodeTypes.SystemMessage.VIEW)) {
            message.setView(node.getProperty(AdmincentralNodeTypes.SystemMessage.VIEW).getString());
        }

        // remove all attributes that are already explicitly treated - see above.
        map.remove(AdmincentralNodeTypes.SystemMessage.TIMESTAMP);
        map.remove(AdmincentralNodeTypes.SystemMessage.SENDER);
        map.remove(AdmincentralNodeTypes.SystemMessage.SUBJECT);
        map.remove(AdmincentralNodeTypes.SystemMessage.MESSAGETYPE);
        map.remove(AdmincentralNodeTypes.SystemMessage.VIEW);
        map.remove(AdmincentralNodeTypes.SystemMessage.CLEARED);

        final Iterator<String> propertyNames = map.keySet().iterator();
        while (propertyNames.hasNext()) {
            final String propertyName = propertyNames.next();
            message.addProperty(propertyName, map.get(propertyName));
        }

        return message;
    }

    private Node getOrCreateUserNode(Session session, String userName) throws RepositoryException {
        String userNodePath = WORKSPACE_PATH + userName;
        return JcrUtils.getOrCreateByPath(userNodePath, USER_NODE_TYPE, session);
    }

    private Node getOrCreateMessageNode(Session session, String userName, Message message)
            throws RepositoryException {
        String messageNodePath = WORKSPACE_PATH + userName + "/" + message.getId();
        return JcrUtils.getOrCreateByPath(messageNodePath, false, USER_NODE_TYPE, MESSAGE_NODE_TYPE, session,
                false);
    }

    private Node getMessageNode(Session session, String userName, String messageId) throws RepositoryException {
        String messageNodePath = WORKSPACE_PATH + userName + "/" + messageId;
        return session.nodeExists(messageNodePath) ? session.getNode(messageNodePath) : null;
    }

    private String getUniqueMessageId(Node userNode) throws RepositoryException {
        int largestIdFound = -1;
        for (Node node : NodeUtil.getNodes(userNode, MESSAGE_NODE_TYPE)) {
            try {
                int nameAsInt = Integer.parseInt(node.getName());
                if (nameAsInt > largestIdFound) {
                    largestIdFound = nameAsInt;
                }
            } catch (NumberFormatException e) {
                logger.warn("Expected name of node " + userNode.getPath() + " to be numeric", e);
            }
        }
        return String.valueOf(largestIdFound + 1);
    }
}