001/*
002 *  Copyright 2025 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.odfweb.observation;
017
018import java.util.Map;
019
020import org.apache.avalon.framework.service.ServiceException;
021import org.apache.avalon.framework.service.ServiceManager;
022import org.apache.avalon.framework.service.Serviceable;
023
024import org.ametys.cms.ObservationConstants;
025import org.ametys.cms.contenttype.ContentTypesHelper;
026import org.ametys.cms.repository.Content;
027import org.ametys.core.observation.Event;
028import org.ametys.core.observation.Observer;
029import org.ametys.odf.skill.workflow.SkillEditionFunction;
030import org.ametys.plugins.odfweb.repository.FirstLevelPageFactory;
031import org.ametys.plugins.odfweb.repository.OdfPageHandler;
032import org.ametys.plugins.repository.AmetysObjectIterable;
033import org.ametys.plugins.repository.AmetysObjectResolver;
034import org.ametys.plugins.repository.query.expression.Expression;
035import org.ametys.plugins.repository.query.expression.VirtualFactoryExpression;
036import org.ametys.runtime.plugin.component.AbstractLogEnabled;
037import org.ametys.web.cache.CacheInvalidationPolicy;
038import org.ametys.web.cache.pageelement.PageElementCache;
039import org.ametys.web.repository.page.Page;
040import org.ametys.web.repository.page.PageQueryHelper;
041
042/**
043 * {@link Observer} for observing skills modification to invalidate cache on front-office.
044 */
045public class InvalidateCacheOnSkillsModifiedObserver extends AbstractLogEnabled implements Observer, Serviceable
046{
047    /** The content types helper */
048    protected ContentTypesHelper _contentTypesHelper;
049    
050    /** The Ametys object resolver */
051    protected AmetysObjectResolver _resolver;
052    
053    /** Cache invalidation policy */
054    protected CacheInvalidationPolicy _cachePolicy;
055
056    /** The zone items cache */
057    protected PageElementCache _zoneItemCache;
058    
059    @Override
060    public void service(ServiceManager manager) throws ServiceException
061    {
062        _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE);
063        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
064        _cachePolicy = (CacheInvalidationPolicy) manager.lookup(CacheInvalidationPolicy.ROLE);
065        _zoneItemCache = (PageElementCache) manager.lookup(PageElementCache.ROLE + "/zoneItem");
066    }
067    
068    public int getPriority()
069    {
070        return MAX_PRIORITY;
071    }
072
073    @Override
074    public boolean supports(Event event)
075    {
076        if (event.getId().equals(ObservationConstants.EVENT_CONTENT_MODIFIED))
077        {
078            Content content = (Content) event.getArguments().get(ObservationConstants.ARGS_CONTENT);
079            return _contentTypesHelper.isInstanceOf(content, SkillEditionFunction.MACRO_SKILL_TYPE);
080        }
081        return false;
082    }
083    
084    @Override
085    public void observe(Event event, Map<String, Object> transientVars) throws Exception
086    {
087        Content macroSkill = (Content) event.getArguments().get(ObservationConstants.ARGS_CONTENT);
088        String catalog = macroSkill.getValue("catalog");
089        
090        // We do not want to computed all programs concerned by the modified skill, so just invalidate all ODF root pages and their sites
091        _getODFRootPages()
092            .stream()
093            .filter(odfRootPage -> catalog.equals(odfRootPage.getValue(OdfPageHandler.CATALOG_DATA_NAME))) // Filter only ODF root pages using the modified macro skill catalog
094            .forEach(odfRootPage -> {
095                try
096                {
097                    _cachePolicy.invalidateCacheOnPageModification(odfRootPage); // Invalidate the cache of the concerned ODF root pages
098                    _zoneItemCache.clear(null, odfRootPage.getSiteName(), "CONTENT"); // Invalidate the zone items cache of the concerned ODF root pages' site
099                }
100                catch (Exception e)
101                {
102                    getLogger().error("Error while invalidating cache for ODF root page '" + odfRootPage.getId() + "' after skill modification.", e);
103                }
104            });
105    }
106    
107    /**
108     * Get the ODF root pages
109     * @return the ODF root pages
110     */
111    protected AmetysObjectIterable<Page> _getODFRootPages()
112    {
113        Expression expression = new VirtualFactoryExpression(FirstLevelPageFactory.class.getName());
114        String query = PageQueryHelper.getPageXPathQuery(null, null, null, expression, null);
115        
116        return _resolver.query(query);
117    }
118}