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