001/* 002 * Copyright 2015 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.web.indexing.observation; 017 018import java.util.Collection; 019import java.util.List; 020import java.util.Map; 021 022import org.apache.avalon.framework.context.Context; 023import org.apache.avalon.framework.context.ContextException; 024import org.apache.avalon.framework.context.Contextualizable; 025import org.apache.avalon.framework.service.ServiceException; 026import org.apache.avalon.framework.service.ServiceManager; 027import org.apache.avalon.framework.service.Serviceable; 028import org.apache.cocoon.components.ContextHelper; 029import org.apache.cocoon.environment.Request; 030import org.apache.commons.lang.StringUtils; 031 032import org.ametys.cms.content.indexing.solr.SolrIndexer; 033import org.ametys.cms.content.indexing.solr.observation.ObserverHelper; 034import org.ametys.cms.indexing.IndexingObserver; 035import org.ametys.cms.repository.Content; 036import org.ametys.core.observation.Event; 037import org.ametys.core.observation.Observer; 038import org.ametys.plugins.repository.AmetysObjectResolver; 039import org.ametys.plugins.repository.RepositoryConstants; 040import org.ametys.plugins.repository.provider.RequestAttributeWorkspaceSelector; 041import org.ametys.runtime.plugin.component.AbstractLogEnabled; 042import org.ametys.web.ObservationConstants; 043import org.ametys.web.WebConstants; 044import org.ametys.web.repository.content.WebContent; 045import org.ametys.web.repository.page.Page; 046import org.ametys.web.repository.page.PageDAO; 047import org.ametys.web.repository.page.ZoneItem.ZoneType; 048import org.ametys.web.search.systemprop.OrphanSystemProperty; 049import org.ametys.web.search.systemprop.PagesSystemProperty; 050 051/** 052 * Observes when a content is made orphan or reaffected to a page and update the index accordingly. 053 */ 054public class ContentOrphanStatusPart2Observer extends AbstractLogEnabled implements IndexingObserver, Serviceable, Contextualizable 055{ 056 /** The Solr indexer. */ 057 protected SolrIndexer _solrIndexer; 058 /** The Ametys object resolver */ 059 protected AmetysObjectResolver _resolver; 060 /** The page DAO */ 061 protected PageDAO _pageDAO; 062 /** The avalon context */ 063 protected Context _context; 064 065 @Override 066 public void service(ServiceManager manager) throws ServiceException 067 { 068 _solrIndexer = (SolrIndexer) manager.lookup(SolrIndexer.ROLE); 069 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 070 _pageDAO = (PageDAO) manager.lookup(PageDAO.ROLE); 071 } 072 073 @Override 074 public void contextualize(Context context) throws ContextException 075 { 076 _context = context; 077 } 078 079 @Override 080 public boolean supports(Event event) 081 { 082 return event.getId().equals(ObservationConstants.EVENT_ZONEITEM_DELETED) 083 || event.getId().equals(ObservationConstants.EVENT_ZONEITEM_ADDED) 084 || event.getId().equals(ObservationConstants.EVENT_ZONEITEM_MODIFIED) 085 || event.getId().equals(ObservationConstants.EVENT_PAGE_CHANGED) 086 || event.getId().equals(ObservationConstants.EVENT_PAGE_MOVED) 087 || event.getId().equals(ObservationConstants.EVENT_PAGE_DELETED) 088 || event.getId().equals(org.ametys.cms.ObservationConstants.EVENT_CONTENT_UNTAG_LIVE) 089 || event.getId().equals(org.ametys.cms.ObservationConstants.EVENT_CONTENT_VALIDATED); 090 } 091 092 @Override 093 public int getPriority() 094 { 095 // Will be processed AFTER live synchronization observers, to have up-to-date pages in live, in order to have correct indexation data 096 return Observer.MAX_PRIORITY + 4000; 097 } 098 099 @Override 100 public void observe(Event event, Map<String, Object> transientVars) throws Exception 101 { 102 if (ObserverHelper.isNotSuspendedObservationForIndexation()) 103 { 104 if (event.getId().equals(ObservationConstants.EVENT_PAGE_CHANGED) || event.getId().equals(ObservationConstants.EVENT_PAGE_MOVED)) 105 { 106 Page page = (Page) event.getArguments().get(ObservationConstants.ARGS_PAGE); 107 if (page != null) 108 { 109 // Update index of contents of page and its sub pages (orphan status may need to be updated) 110 _updatePageContents(page, transientVars); 111 } 112 } 113 else if (event.getId().equals(ObservationConstants.EVENT_PAGE_DELETED)) 114 { 115 @SuppressWarnings("unchecked") 116 List<Content> contents = (List<Content>) event.getArguments().get(ObservationConstants.ARGS_PAGE_CONTENTS); 117 String pageId = (String) event.getArguments().get(ObservationConstants.ARGS_PAGE_ID); 118 if (contents != null) 119 { 120 for (Content content : contents) 121 { 122 _updateContentProperties(content, pageId, transientVars); 123 } 124 } 125 } 126 else if (event.getId().equals(org.ametys.cms.ObservationConstants.EVENT_CONTENT_UNTAG_LIVE) || event.getId().equals(org.ametys.cms.ObservationConstants.EVENT_CONTENT_VALIDATED)) 127 { 128 Content content = (Content) event.getArguments().get(org.ametys.cms.ObservationConstants.ARGS_CONTENT); 129 if (content instanceof WebContent webContent) 130 { 131 // When a content is unpublished or validated its pages and its subpages may be removed or added to live workspace 132 // The orphan status of their contents may need to be updated 133 Collection<Page> referencingPages = webContent.getReferencingPages(); 134 for (Page page : referencingPages) 135 { 136 // Get all contents of page and subpages 137 _updatePageContents(page, transientVars); 138 } 139 } 140 } 141 else 142 { 143 ZoneType zoneType = (ZoneType) event.getArguments().get(ObservationConstants.ARGS_ZONE_TYPE); 144 if (zoneType == ZoneType.CONTENT) 145 { 146 Content content = (Content) event.getArguments().get(ObservationConstants.ARGS_ZONE_ITEM_CONTENT); 147 if (content != null) 148 { 149 _updateContentProperties(content, null, transientVars); 150 } 151 } 152 } 153 } 154 } 155 156 @SuppressWarnings("unchecked") 157 private boolean _pageWasInLive(String pageId, Map<String, Object> transientVars) 158 { 159 return ((List<String>) transientVars.getOrDefault(ContentOrphanStatusPart1Observer.PAGES_IN_LIVE_BEFORE_SYNCHRONIZATION, List.of())).contains(pageId); 160 } 161 162 private void _updatePageContents (Page page, Map<String, Object> transientVars) throws Exception 163 { 164 for (Content content : _pageDAO.getPageContents(page)) 165 { 166 _updateContentProperties(content, page.getId(), transientVars); 167 } 168 } 169 170 /** 171 * Update content properties 172 * @param content the content 173 * @param pageId the page id 174 * @param transientVars the transvient variables 175 * @throws Exception if an error occurred 176 */ 177 protected void _updateContentProperties(Content content, String pageId, Map<String, Object> transientVars) throws Exception 178 { 179 // default workspace 180 _updateProperties(content, RepositoryConstants.DEFAULT_WORKSPACE); 181 182 // live workspace 183 Request request = ContextHelper.getRequest(_context); 184 String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 185 try 186 { 187 // Force the workspace. 188 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, WebConstants.LIVE_WORKSPACE); 189 190 if (_resolver.hasAmetysObjectForId(content.getId())) 191 { 192 Content liveContent = _resolver.resolveById(content.getId(), null); 193 if (StringUtils.isBlank(pageId)) 194 { 195 _updateProperties(liveContent, WebConstants.LIVE_WORKSPACE); 196 } 197 else 198 { 199 boolean isPageInLive = _resolver.hasAmetysObjectForId(pageId); 200 boolean pageWasInLive = _pageWasInLive(pageId, transientVars); 201 if (isPageInLive ^ pageWasInLive) // A XOR B : we update content properties in live workspace if page status has changed 202 { 203 _updateProperties(liveContent, WebConstants.LIVE_WORKSPACE); 204 } 205 } 206 } 207 } 208 finally 209 { 210 // Restore workspace 211 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp); 212 } 213 } 214 215 private void _updateProperties(Content content, String workspace) throws Exception 216 { 217 if (content instanceof WebContent) 218 { 219 // Update orphan status 220 _solrIndexer.updateSystemProperty(content, OrphanSystemProperty.SYSTEM_PROPERTY_ID, workspace); 221 // Update pageIds 222 _solrIndexer.updateSystemProperty(content, PagesSystemProperty.SYSTEM_PROPERTY_ID, workspace); 223 } 224 } 225}