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}