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