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