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.plugins.repository.events; 017 018import java.util.Date; 019import java.util.GregorianCalendar; 020 021import javax.jcr.Node; 022import javax.jcr.NodeIterator; 023import javax.jcr.RepositoryException; 024import javax.jcr.Session; 025import javax.jcr.query.Query; 026 027import org.apache.avalon.framework.component.Component; 028import org.apache.cocoon.util.HashUtil; 029import org.apache.commons.lang.StringUtils; 030import org.apache.commons.lang.time.DateFormatUtils; 031 032import org.ametys.core.user.UserIdentity; 033import org.ametys.plugins.repository.RepositoryConstants; 034import org.ametys.plugins.repository.query.SortCriteria; 035import org.ametys.plugins.repository.query.expression.Expression; 036import org.ametys.plugins.repository.query.expression.Expression.Operator; 037 038/** 039 * Helper for implementing {@link EventHolder} in JCR. 040 */ 041public final class JCREventHelper implements Component 042{ 043 private static final String __EVENT_HOLDER_NODE_NAME = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":events"; 044 045 private static final String __NODETYPE_EVENT = RepositoryConstants.NAMESPACE_PREFIX + ":event"; 046 047 private static final String __NODETYPE_EVENT_ELEMENT = RepositoryConstants.NAMESPACE_PREFIX + ":eventElement"; 048 049 private static final String __EVENT_NAME_PREFIX = "ametys-event_"; 050 051 private JCREventHelper() 052 { 053 // Nothing 054 } 055 056 /** 057 * Get the node holding the JCR events under the given node 058 * @param node The parent node 059 * @return the events' root node 060 * @throws RepositoryException if an error occurred 061 */ 062 public static Node getEventsRootNode(Node node) throws RepositoryException 063 { 064 if (node.hasNode(__EVENT_HOLDER_NODE_NAME)) 065 { 066 return node.getNode(__EVENT_HOLDER_NODE_NAME); 067 } 068 069 return node.addNode(__EVENT_HOLDER_NODE_NAME, "nt:unstructured"); 070 } 071 072 /** 073 * Returns the event child nodes 074 * @param eventHolder The event holder 075 * @return The event child nodes 076 * @throws RepositoryException if an error occurred 077 */ 078 public static NodeIterator getEvents(EventHolder eventHolder) throws RepositoryException 079 { 080 return getEvents(eventHolder, null); 081 } 082 083 /** 084 * Returns the events sorted by ascending date 085 * @param eventHolder The event holder 086 * @param types The types of events to return. Can be <code>null</code> to get all events 087 * @return The event child nodes 088 * @throws RepositoryException if an error occurred 089 */ 090 public static NodeIterator getEvents(EventHolder eventHolder, String[] types) throws RepositoryException 091 { 092 EventTypeExpression eventExpr = null; 093 if (types != null && types.length > 0) 094 { 095 eventExpr = new EventTypeExpression(Operator.EQ, types); 096 } 097 098 SortCriteria sortCriteria = new SortCriteria(); 099 sortCriteria.addJCRPropertyCriterion(EventType.EVENT_DATE, false, false); 100 101 String xPathQuery = getEventXPathQuery(eventHolder, eventExpr, sortCriteria); 102 103 Session session = eventHolder.getEventsRootNode().getSession(); 104 105 @SuppressWarnings("deprecation") 106 Query query = session.getWorkspace().getQueryManager().createQuery(xPathQuery, Query.XPATH); 107 return query.execute().getNodes(); 108 } 109 110 /** 111 * Creates the XPath query corresponding to specified {@link Expression}. 112 * @param eventHolder the event holder 113 * @param eventExpression the query predicates. 114 * @param sortCriteria the sort criteria. 115 * @return the created XPath query. 116 * @throws RepositoryException if an error occurred 117 */ 118 public static String getEventXPathQuery(EventHolder eventHolder, Expression eventExpression, SortCriteria sortCriteria) throws RepositoryException 119 { 120 String predicats = null; 121 122 if (eventExpression != null) 123 { 124 predicats = StringUtils.trimToNull(eventExpression.build()); 125 } 126 127 String xpathQuery = "/jcr:root" 128 + eventHolder.getEventsRootNode().getPath() 129 + "//element(*, ametys:event)" 130 + (predicats != null ? "[" + predicats + "]" : "") 131 + ((sortCriteria != null) ? (" " + sortCriteria.build()) : ""); 132 return xpathQuery; 133 } 134 135 /** 136 * Add an event node 137 * @param eventHolder The event holder 138 * @param eventDate The event's date 139 * @param eventType The event's type 140 * @param author The event's author 141 * @return The created event node 142 * @throws RepositoryException if an error occurred 143 */ 144 public static Node addEventNode (EventHolder eventHolder, Date eventDate, String eventType, UserIdentity author) throws RepositoryException 145 { 146 Node contextNode = eventHolder.getEventsRootNode(); 147 148 String eventName = __EVENT_NAME_PREFIX + DateFormatUtils.format(eventDate, "yyyy_MM_dd_HH_mm_ss_SSS"); 149 String[] hash = _getHashedPath(eventName); 150 151 for (String hashPart : hash) 152 { 153 if (!contextNode.hasNode(hashPart)) 154 { 155 contextNode = contextNode.addNode(hashPart, __NODETYPE_EVENT_ELEMENT); 156 } 157 else 158 { 159 contextNode = contextNode.getNode(hashPart); 160 } 161 } 162 163 Node eventNode = contextNode.addNode(eventName, __NODETYPE_EVENT); 164 165 // Date 166 GregorianCalendar gc = new GregorianCalendar(); 167 gc.setTime(eventDate); 168 169 eventNode.setProperty(EventType.EVENT_DATE, gc); 170 171 // Type 172 eventNode.setProperty(EventType.EVENT_TYPE, eventType); 173 174 // Author 175 Node authorNode = eventNode.addNode(EventType.EVENT_AUTHOR); 176 authorNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ":login", author.getLogin()); 177 authorNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ":population", author.getPopulationId()); 178 179 return eventNode; 180 } 181 182 /** 183 * Computes a hashed path in the JCR tree from the name of the child object.<br> 184 * Subclasses may override this method to provide a more suitable hash function.<br> 185 * This implementation relies on the buzhash algorithm. 186 * This method MUST return an array of the same length for each name. 187 * @param name the name of the child object 188 * @return a hashed path of the name. 189 */ 190 private static String[] _getHashedPath(String name) 191 { 192 long hash = Math.abs(HashUtil.hash(name)); 193 String hashStr = Long.toString(hash, 16); 194 hashStr = StringUtils.leftPad(hashStr, 4, '0'); 195 196 return new String[]{hashStr.substring(0, 2), hashStr.substring(2, 4)}; 197 } 198}