001/* 002 * Copyright 2010 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 */ 016 017package org.ametys.web.live; 018 019import java.util.Map; 020import java.util.regex.Pattern; 021 022import javax.jcr.ItemExistsException; 023import javax.jcr.Node; 024import javax.jcr.Property; 025import javax.jcr.Repository; 026import javax.jcr.Session; 027import javax.jcr.Value; 028import javax.jcr.observation.Event; 029import javax.jcr.observation.EventIterator; 030import javax.jcr.observation.EventListener; 031 032import org.apache.avalon.framework.logger.AbstractLogEnabled; 033import org.apache.avalon.framework.logger.Logger; 034import org.apache.commons.collections.PredicateUtils; 035 036import org.ametys.plugins.repository.jcr.NodeTypeHelper; 037import org.ametys.web.WebConstants; 038import org.ametys.web.synchronization.SynchronizeComponent; 039 040/** 041 * JCR {@link EventListener} for monitoring changes in the default workspace and reflect then in the live workspace 042 */ 043public class LiveWorkspaceListener extends AbstractLogEnabled implements EventListener 044{ 045 /** Avalon Role */ 046 public static final String ROLE = LiveWorkspaceListener.class.getName(); 047 048 private Repository _repository; 049 050 private SynchronizeComponent _synchroComponent; 051 052 private Pattern[] _excludedPaths; 053 054 /** 055 * Constructor 056 * @param repository the JCR Repository 057 * @param synchroHelper the synchronize helper 058 * @param logger logger for traces 059 */ 060 public LiveWorkspaceListener(Repository repository, SynchronizeComponent synchroHelper, Logger logger) 061 { 062 this(repository, synchroHelper, logger, null); 063 } 064 065 /** 066 * Constructor 067 * @param repository the JCR Repository 068 * @param synchroHelper the synchronize helper 069 * @param logger logger for traces 070 * @param excludedPaths The pattern of paths to exclude 071 */ 072 public LiveWorkspaceListener(Repository repository, SynchronizeComponent synchroHelper, Logger logger, Pattern[] excludedPaths) 073 { 074 _repository = repository; 075 _synchroComponent = synchroHelper; 076 enableLogging(logger); 077 _excludedPaths = excludedPaths; 078 } 079 080 @Override 081 public void onEvent(EventIterator events) 082 { 083 Session session = null; 084 Session liveSession = null; 085 086 try 087 { 088 session = _repository.login(); 089 liveSession = _repository.login(WebConstants.LIVE_WORKSPACE); 090 091 while (events.hasNext()) 092 { 093 Event event = events.nextEvent(); 094 String path = event.getPath(); 095 096 if (_isValidPath(path)) 097 { 098 _switchOnType(session, liveSession, event, path); 099 } 100 } 101 102 if (liveSession.hasPendingChanges()) 103 { 104 liveSession.save(); 105 } 106 } 107 catch (Exception e) 108 { 109 getLogger().warn("Unable to synchronize live workspace", e); 110 } 111 finally 112 { 113 if (session != null) 114 { 115 session.logout(); 116 } 117 118 if (liveSession != null) 119 { 120 liveSession.logout(); 121 } 122 } 123 } 124 125 private boolean _isValidPath (String path) 126 { 127 if (_excludedPaths == null || _excludedPaths.length == 0) 128 { 129 return true; 130 } 131 132 for (Pattern pattern : _excludedPaths) 133 { 134 if (pattern.matcher(path).matches()) 135 { 136 return false; 137 } 138 } 139 140 return true; 141 142 } 143 144 private void _switchOnType(Session session, Session liveSession, Event event, String path) throws Exception 145 { 146 switch (event.getType()) 147 { 148 case Event.NODE_ADDED : 149 Node node = (Node) session.getItem(path); 150 String parentPath = node.getParent().getPath(); 151 152 if (liveSession.itemExists(parentPath) && !liveSession.itemExists(path) && !NodeTypeHelper.getNodeTypeName(node).startsWith("oswf:") && !node.isNodeType("ametys:content")) 153 { 154 // if source node exists and parent dest node also exists, copy the whole sub tree 155 // do not simply copy the node, as events may be unordered 156 Node parentNode = (Node) liveSession.getItem(parentPath); 157 158 try 159 { 160 Node clonedNode = _synchroComponent.addNodeWithUUID(node, parentNode, node.getName()); 161 _synchroComponent.cloneNodeAndPreserveUUID(node, clonedNode, PredicateUtils.truePredicate(), PredicateUtils.truePredicate()); 162 } 163 catch (ItemExistsException ex) 164 { 165 // in case of moved nodes, we may receive first a "node added" event, then a "node moved" event. 166 // in that case, the addNodeWithUUID fail. Just ignore it. 167 } 168 } 169 170 break; 171 case Event.NODE_MOVED : 172 _nodeMoved(liveSession, event, path); 173 break; 174 case Event.NODE_REMOVED : 175 if (liveSession.nodeExists(path) && !session.nodeExists(path)) 176 { 177 liveSession.getItem(path).remove(); 178 } 179 break; 180 case Event.PROPERTY_REMOVED : 181 if (liveSession.propertyExists(path) && !session.propertyExists(path)) 182 { 183 liveSession.getItem(path).remove(); 184 } 185 break; 186 case Event.PROPERTY_ADDED : 187 case Event.PROPERTY_CHANGED : 188 Property property = (Property) session.getItem(path); 189 String name = property.getName(); 190 String parent = property.getParent().getPath(); 191 192 if (liveSession.itemExists(parent) && !property.getDefinition().isProtected() && !property.getName().startsWith("oswf:")) 193 { 194 if (property.isMultiple()) 195 { 196 Value[] values = property.getValues(); 197 ((Node) liveSession.getItem(parent)).setProperty(name, values); 198 } 199 else 200 { 201 Value value = property.getValue(); 202 ((Node) liveSession.getItem(parent)).setProperty(name, value); 203 } 204 } 205 206 break; 207 default : 208 break; 209 } 210 } 211 212 private void _nodeMoved(Session liveSession, Event event, String path) throws Exception 213 { 214 Map<String, String> info = event.getInfo(); 215 String srcChildRelPath = info.get("srcChildRelPath"); 216 217 if (srcChildRelPath != null) 218 { 219 // in case of Node.orderBefore() 220 if (liveSession.nodeExists(path)) 221 { 222 Node parentNode = liveSession.getItem(path).getParent(); 223 224 String destChildRelPath = info.get("destChildRelPath"); 225 226 if (parentNode.hasNode(srcChildRelPath)) 227 { 228 parentNode.orderBefore(srcChildRelPath, destChildRelPath); 229 } 230 } 231 } 232 else 233 { 234 // in case of Session.move() or Workspace.move() 235 String srcAbsPath = info.get("srcAbsPath"); 236 String destAbsPath = info.get("destAbsPath"); 237 if (liveSession.itemExists(srcAbsPath) && !liveSession.itemExists(destAbsPath)) 238 { 239 liveSession.move(srcAbsPath, destAbsPath); 240 } 241 } 242 } 243}