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.plugins.odfweb.observation;
017
018import java.util.Collections;
019import java.util.Map;
020import java.util.Set;
021
022import org.apache.avalon.framework.context.ContextException;
023import org.apache.avalon.framework.context.Contextualizable;
024import org.apache.avalon.framework.service.ServiceException;
025import org.apache.avalon.framework.service.ServiceManager;
026import org.apache.avalon.framework.service.Serviceable;
027import org.apache.cocoon.components.ContextHelper;
028import org.apache.cocoon.environment.Context;
029import org.apache.cocoon.environment.Request;
030
031import org.ametys.cms.ObservationConstants;
032import org.ametys.cms.repository.Content;
033import org.ametys.core.observation.Event;
034import org.ametys.core.observation.Observer;
035import org.ametys.odf.ODFHelper;
036import org.ametys.odf.ProgramItem;
037import org.ametys.odf.course.Course;
038import org.ametys.odf.coursepart.CoursePart;
039import org.ametys.odf.program.Program;
040import org.ametys.odf.program.SubProgram;
041import org.ametys.plugins.odfweb.repository.FirstLevelPageFactory;
042import org.ametys.plugins.repository.AmetysObjectIterable;
043import org.ametys.plugins.repository.AmetysObjectResolver;
044import org.ametys.plugins.repository.RepositoryConstants;
045import org.ametys.plugins.repository.provider.RequestAttributeWorkspaceSelector;
046import org.ametys.plugins.repository.query.expression.Expression;
047import org.ametys.plugins.repository.query.expression.VirtualFactoryExpression;
048import org.ametys.runtime.plugin.component.AbstractLogEnabled;
049import org.ametys.web.repository.page.Page;
050import org.ametys.web.repository.page.PageQueryHelper;
051/**
052 * Abstract {@link Observer} for observing events on a Program or Course.
053 */
054public abstract class AbstractODFObserver extends AbstractLogEnabled implements Observer, Serviceable, Contextualizable
055{
056    /** The context. */
057    protected org.apache.avalon.framework.context.Context _context;
058    /** Cocoon context. */
059    protected Context _cocoonContext;
060    /** Ametys object resolver. */
061    protected AmetysObjectResolver _resolver;
062    /** The ODF helper */
063    protected ODFHelper _odfHelper;
064    
065    @Override
066    public void contextualize(org.apache.avalon.framework.context.Context context) throws ContextException
067    {
068        _context = context;
069        _cocoonContext = (Context) context.get(org.apache.cocoon.Constants.CONTEXT_ENVIRONMENT_CONTEXT);
070    }
071
072    @Override
073    public void service(ServiceManager manager) throws ServiceException
074    {
075        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
076        _odfHelper = (ODFHelper) manager.lookup(ODFHelper.ROLE);
077    }
078    
079    /**
080     * The workspace to use. Default to {@link RepositoryConstants#DEFAULT_WORKSPACE default}, 
081     * override this method to work on a different workspace
082     * @return The workspace to use
083     */
084    protected String _workspaceToUse()
085    {
086        return RepositoryConstants.DEFAULT_WORKSPACE;
087    }
088
089    @Override
090    public void observe(Event event, Map<String, Object> transientVars) throws Exception
091    {
092        Request request = ContextHelper.getRequest(_context);
093        
094        // Retrieve current workspace
095        String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request);
096        
097        try
098        {
099            // Switch to required workspace
100            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, _workspaceToUse());
101            
102            AmetysObjectIterable<Page> rootPages = _getODFRootPages();
103            if (rootPages.getSize() == 0)
104            {
105                getLogger().debug("There is no ODF root page, nothing to invalidate");
106                return;
107            }
108            
109            Set<Program> programs = _getPrograms(event);
110            if (programs.size() == 0)
111            {
112                getLogger().debug("There is no concerned programs");
113                return;
114            }
115            
116            for (Page odfRootPage : rootPages)
117            {
118                _internalObserve(event, transientVars, odfRootPage, programs, _getSubProgram(event), _getCourse(event));
119            }
120        }
121        catch (Exception e)
122        {
123            getLogger().error("Unable to observe event: {}", event, e);
124        }
125        finally
126        {
127            // Restore current workspace
128            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp);
129        }
130    }
131    
132    /**
133     * Do the actual work.
134     * @param event the event.
135     * @param transientVars transientVars passed from one Observer to another when processing a single Event. 
136     * This may allow optimizations between observers.
137     * @param odfRootPage the Page holding the virtual factory.
138     * @param rootPrograms the root programs.
139     * @param subProgram The subprogram. Can be null
140     * @param course The course. Can be null.
141     * @throws Exception if an error occured
142     */
143    protected abstract void _internalObserve(Event event, Map<String, Object> transientVars, Page odfRootPage, Set<Program> rootPrograms, SubProgram subProgram, Course course) throws Exception;
144    
145    /**
146     * Get the ODF root pages
147     * @return the ODF root pages
148     */
149    protected AmetysObjectIterable<Page> _getODFRootPages()
150    {
151        Expression expression = new VirtualFactoryExpression(FirstLevelPageFactory.class.getName());
152        String query = PageQueryHelper.getPageXPathQuery(null, null, null, expression, null);
153        
154        return _resolver.query(query);
155    }
156    
157    /**
158     * Retrieve the target of the observer
159     * @param event The event
160     * @return The target
161     * @throws Exception if failed to get content
162     */ 
163    protected Content _getContentArgument(Event event) throws Exception
164    {
165        return (Content) event.getArguments().get(ObservationConstants.ARGS_CONTENT);
166    }
167    
168    /**
169     * Get the target abstract program
170     * @param event the event
171     * @return the target abstract program or <code>null</code>
172     * @throws Exception if failed to get content
173     */
174    protected SubProgram _getSubProgram(Event event) throws Exception
175    {
176        Content content = _getContentArgument(event);
177        
178        if (content instanceof SubProgram)
179        {
180            return (SubProgram) content;
181        }
182        return null;
183    }
184    
185    /**
186     * Get the target course
187     * @param event the event
188     * @return the target course or <code>null</code>
189     * @throws Exception if failed to get content
190     */
191    protected Course _getCourse(Event event) throws Exception
192    {
193        Content content = _getContentArgument(event);
194        
195        if (content instanceof Course)
196        {
197            return (Course) content;
198        }
199        return null;
200    }
201    
202    /**
203     * Get the target programs
204     * @param event the event
205     * @return the target programs in a List
206     * @throws Exception if failed to get content 
207     */
208    protected Set<Program> _getPrograms(Event event) throws Exception
209    {
210        Content content = _getContentArgument(event);
211        
212        // ODF-1825 We should invalidate the cache for any program or course item because it can impacts the display of any other ODF content
213        
214        if (content instanceof Program program)
215        {
216            return Collections.singleton(program);
217        }
218        else if (content instanceof ProgramItem programItem)
219        {
220            return _odfHelper.getParentPrograms(programItem);
221        }
222        else if (content instanceof CoursePart coursePart)
223        {
224            return _odfHelper.getParentPrograms(coursePart);
225        }
226        else
227        {
228            getLogger().debug("This observer only handles ODF contents for virtual pages");
229            return Collections.emptySet();
230        }
231    }
232}
233