001/* 002 * Copyright 2017 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.web.cache; 017 018import java.util.HashSet; 019import java.util.Map; 020import java.util.Set; 021import java.util.regex.Matcher; 022import java.util.regex.Pattern; 023 024import org.apache.avalon.framework.service.ServiceException; 025import org.apache.avalon.framework.service.ServiceManager; 026import org.apache.avalon.framework.service.Serviceable; 027import org.apache.jackrabbit.util.ISO9075; 028 029import org.ametys.core.observation.Event; 030import org.ametys.core.observation.Observer; 031import org.ametys.plugins.explorer.ObservationConstants; 032import org.ametys.plugins.repository.AmetysObjectIterable; 033import org.ametys.plugins.repository.AmetysObjectIterator; 034import org.ametys.plugins.repository.AmetysObjectResolver; 035import org.ametys.runtime.plugin.component.AbstractLogEnabled; 036import org.ametys.web.repository.page.Page; 037import org.ametys.web.repository.page.ZoneItem; 038import org.ametys.web.repository.site.Site; 039import org.ametys.web.repository.site.SiteManager; 040 041/** 042 * Invalidate the page cache based on explorer events. 043 */ 044public class InvalidatePageCacheExplorerObserver extends AbstractLogEnabled implements Observer, Serviceable 045{ 046 private static final Set<String> _TYPES = new HashSet<>(); 047 static 048 { 049 _TYPES.add("SERVICE:org.ametys.web.service.AttachmentsService"); 050 _TYPES.add("SERVICE:org.ametys.web.service.ExplorerFolderService"); 051 } 052 053 private static final Set<String> __XPATH_ZONEITEM_SERVICE = new HashSet<>(); 054 static 055 { 056 __XPATH_ZONEITEM_SERVICE.add("//element(*, ametys:page)/ametys-internal:zones//ametys:zoneItem[@ametys-internal:service=\"org.ametys.web.service.AttachmentsService\"]"); 057 __XPATH_ZONEITEM_SERVICE.add("//element(*, ametys:page)/ametys-internal:zones//ametys:zoneItem[@ametys-internal:service=\"org.ametys.web.service.ExplorerFolderService\"]"); 058 } 059 060 private static final Pattern __RESOURCE_PATTERN = Pattern.compile("^.*/ametys-internal:sites/([^/]+)/ametys-internal:(.*)$"); 061 private static final Pattern __ROOT_SITE_RESOURCE_PATTERN = Pattern.compile("^.*/ametys-internal:sites/[^/]+/[^/]+/([^/]+)/ametys-internal:(.*)$"); 062 063 064 private SiteManager _siteManager; 065 private AmetysObjectResolver _resolver; 066 067 @Override 068 public void service(ServiceManager manager) throws ServiceException 069 { 070 _siteManager = (SiteManager) manager.lookup(SiteManager.ROLE); 071 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 072 } 073 074 @Override 075 public int getPriority(Event event) 076 { 077 return MAX_PRIORITY; 078 } 079 080 @Override 081 public boolean supports(Event event) 082 { 083 String eventType = event.getId(); 084 085 return eventType.equals(ObservationConstants.EVENT_RESOURCE_RENAMED) 086 || eventType.equals(ObservationConstants.EVENT_RESOURCE_CREATED) 087 || eventType.equals(ObservationConstants.EVENT_RESOURCE_DELETED) 088 || eventType.equals(ObservationConstants.EVENT_RESOURCE_MOVED) 089 || eventType.equals(ObservationConstants.EVENT_RESOURCE_UPDATED) 090 || eventType.equals(ObservationConstants.EVENT_COLLECTION_RENAMED) 091 || eventType.equals(ObservationConstants.EVENT_COLLECTION_MOVED) 092 || eventType.equals(ObservationConstants.EVENT_COLLECTION_COPIED) 093 || eventType.equals(ObservationConstants.EVENT_COLLECTION_DELETED); 094 } 095 096 @Override 097 public void observe(Event event, Map<String, Object> transientVars) throws Exception 098 { 099 String siteName = getSiteName(event); 100 101 if (siteName != null) 102 { 103 Site site = _siteManager.getSite(siteName); 104 doObserve(site, event); 105 } 106 else 107 { 108 doObserve(null, event); 109 } 110 } 111 112 private void doObserve(Site site, Event event) 113 { 114 if (getLogger().isInfoEnabled()) 115 { 116 getLogger().info("New event on explorer resources : " + event + ", invalidating site cache"); 117 } 118 119 try 120 { 121 Set<Page> clearedPages = new HashSet<>(); 122 for (String xpathZoneItem : __XPATH_ZONEITEM_SERVICE) 123 { 124 String xpathQuery = xpathZoneItem; 125 if (site != null) 126 { 127 String relativeJcrPath = site.getNode().getPath().substring(AmetysObjectResolver.ROOT_REPO.length() + 1); 128 xpathQuery = "/" + ISO9075.encodePath(relativeJcrPath) + xpathQuery; 129 } 130 try (AmetysObjectIterable<ZoneItem> zoneItems = _resolver.query(xpathQuery)) 131 { 132 AmetysObjectIterator<ZoneItem> it = zoneItems.iterator(); 133 while (it.hasNext()) 134 { 135 ZoneItem zoneItem = it.next(); 136 Page page = zoneItem.getZone().getPage(); 137 138 if (!clearedPages.contains(page)) 139 { 140 CacheHelper.invalidateCache(page, getLogger()); 141 clearedPages.add(page); 142 } 143 } 144 } 145 } 146 } 147 catch (Exception e) 148 { 149 if (site != null) 150 { 151 getLogger().error("Unable ot invalidate cache for site '" + site.getName() + "' pages containing a service with explorer resources", e); 152 } 153 else 154 { 155 getLogger().error("Unable ot invalidate cache for all sites pages containing a service with explorer resources", e); 156 } 157 } 158 } 159 160 /** 161 * Get the origin of the event. 162 * @param event the event. 163 * @return the origin site name. 164 */ 165 protected String getSiteName(Event event) 166 { 167 String path = null; 168 169 Map<String, Object> args = event.getArguments(); 170 if (event.getId().equals(ObservationConstants.EVENT_RESOURCE_CREATED)) 171 { 172 // Multiple resources may have been created, look for to parent path 173 path = (String) args.get(ObservationConstants.ARGS_PARENT_PATH); 174 } 175 else 176 { 177 path = (String) event.getArguments().get(ObservationConstants.ARGS_PATH); 178 } 179 180 Matcher matcher1 = __RESOURCE_PATTERN.matcher(path); 181 Matcher matcher2 = __ROOT_SITE_RESOURCE_PATTERN.matcher(path); 182 183 String siteName = null; 184 185 if (matcher1.matches()) 186 { 187 siteName = matcher1.group(1); 188 } 189 else if (matcher2.matches()) 190 { 191 siteName = matcher2.group(1); 192 } 193 194 return siteName; 195 } 196 197}