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