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)
215        {
216            return Collections.singleton((Program) content);
217        }
218        else if (content instanceof ProgramItem)
219        {
220            return ((ProgramItem) content).getRootPrograms();
221        }
222        else if (content instanceof CoursePart)
223        {
224            return ((CoursePart) content).getRootPrograms();
225        }
226        else
227        {
228            getLogger().debug("This observer only handles ODF contents for virtual pages");
229            return Collections.emptySet();
230        }
231    }
232}
233