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 * Returns events sorted by ascending date 112 * The method search for events in all the repository and not a specific {@link EventHolder}. 113 * @param session The session to use to find the query manager 114 * @param filterExpression The {@link Expression} used to filter the result. Can be {@code null}. 115 * @return The event child nodes 116 * @throws RepositoryException if an error occurred 117 */ 118 public static NodeIterator getEvents(Session session, Expression filterExpression) throws RepositoryException 119 { 120 SortCriteria sortCriteria = new SortCriteria(); 121 sortCriteria.addJCRPropertyCriterion(EventType.EVENT_DATE, false, false); 122 123 String xPathQuery = getEventXPathQuery(null, filterExpression, sortCriteria); 124 125 @SuppressWarnings("deprecation") 126 Query query = session.getWorkspace().getQueryManager().createQuery(xPathQuery, Query.XPATH); 127 return query.execute().getNodes(); 128 } 129 130 /** 131 * Creates the XPath query corresponding to specified {@link Expression}. 132 * @param eventHolder the event holder 133 * @param eventExpression the query predicates. 134 * @param sortCriteria the sort criteria. 135 * @return the created XPath query. 136 * @throws RepositoryException if an error occurred 137 */ 138 public static String getEventXPathQuery(EventHolder eventHolder, Expression eventExpression, SortCriteria sortCriteria) throws RepositoryException 139 { 140 String predicats = null; 141 142 if (eventExpression != null) 143 { 144 predicats = StringUtils.trimToNull(eventExpression.build()); 145 } 146 147 StringBuilder sb = new StringBuilder(); 148 if (eventHolder != null) 149 { 150 sb.append("/jcr:root").append(eventHolder.getEventsRootNode().getPath()); 151 } 152 sb.append(" //element(*, ametys:event)"); 153 if (predicats != null) 154 { 155 sb.append("[").append(predicats).append("]"); 156 } 157 if (sortCriteria != null) 158 { 159 sb.append(" ").append(sortCriteria.build()); 160 } 161 return sb.toString(); 162 } 163 164 /** 165 * Add an event node 166 * @param eventHolder The event holder 167 * @param eventDate The event's date 168 * @param eventType The event's type 169 * @param author The event's author 170 * @return The created event node 171 * @throws RepositoryException if an error occurred 172 */ 173 public static Node addEventNode (EventHolder eventHolder, Date eventDate, String eventType, UserIdentity author) throws RepositoryException 174 { 175 Node contextNode = eventHolder.getEventsRootNode(); 176 177 String eventName = __EVENT_NAME_PREFIX + DateFormatUtils.format(eventDate, "yyyy_MM_dd_HH_mm_ss_SSS"); 178 String[] hash = _getHashedPath(eventName); 179 180 for (String hashPart : hash) 181 { 182 if (!contextNode.hasNode(hashPart)) 183 { 184 contextNode = contextNode.addNode(hashPart, __NODETYPE_EVENT_ELEMENT); 185 } 186 else 187 { 188 contextNode = contextNode.getNode(hashPart); 189 } 190 } 191 192 Node eventNode = contextNode.addNode(eventName, __NODETYPE_EVENT); 193 194 // Date 195 GregorianCalendar gc = new GregorianCalendar(); 196 gc.setTime(eventDate); 197 198 eventNode.setProperty(EventType.EVENT_DATE, gc); 199 200 // Type 201 eventNode.setProperty(EventType.EVENT_TYPE, eventType); 202 203 // Author 204 Node authorNode = eventNode.addNode(EventType.EVENT_AUTHOR); 205 authorNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ":login", author.getLogin()); 206 authorNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ":population", author.getPopulationId()); 207 208 return eventNode; 209 } 210 211 /** 212 * Computes a hashed path in the JCR tree from the name of the child object.<br> 213 * Subclasses may override this method to provide a more suitable hash function.<br> 214 * This implementation relies on the buzhash algorithm. 215 * This method MUST return an array of the same length for each name. 216 * @param name the name of the child object 217 * @return a hashed path of the name. 218 */ 219 private static String[] _getHashedPath(String name) 220 { 221 long hash = Math.abs(HashUtil.hash(name)); 222 String hashStr = Long.toString(hash, 16); 223 hashStr = StringUtils.leftPad(hashStr, 4, '0'); 224 225 return new String[]{hashStr.substring(0, 2), hashStr.substring(2, 4)}; 226 } 227}