001/* 002 * Copyright 2024 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.content.repair; 017 018import javax.jcr.ItemNotFoundException; 019import javax.jcr.Node; 020import javax.jcr.Repository; 021import javax.jcr.RepositoryException; 022import javax.jcr.Session; 023 024import org.apache.avalon.framework.service.ServiceException; 025import org.apache.avalon.framework.service.ServiceManager; 026import org.apache.cocoon.components.ContextHelper; 027import org.apache.cocoon.environment.Request; 028import org.apache.commons.lang3.ArrayUtils; 029import org.apache.solr.client.solrj.SolrClient; 030import org.quartz.JobExecutionContext; 031 032import org.ametys.cms.CmsConstants; 033import org.ametys.cms.content.indexing.solr.SolrIndexer; 034import org.ametys.cms.repository.Content; 035import org.ametys.cms.repository.ContentQueryHelper; 036import org.ametys.cms.repository.DefaultContent; 037import org.ametys.cms.search.solr.SolrClientProvider; 038import org.ametys.core.schedule.Schedulable; 039import org.ametys.core.schedule.progression.ContainerProgressionTracker; 040import org.ametys.plugins.core.impl.schedule.AbstractStaticSchedulable; 041import org.ametys.plugins.repository.AmetysObjectIterable; 042import org.ametys.plugins.repository.AmetysObjectResolver; 043import org.ametys.plugins.repository.AmetysRepositoryException; 044import org.ametys.plugins.repository.RepositoryConstants; 045import org.ametys.plugins.repository.provider.RequestAttributeWorkspaceSelector; 046import org.ametys.runtime.i18n.I18nizableText; 047import org.ametys.web.WebConstants; 048import org.ametys.web.synchronization.SynchronizeComponent; 049 050/** 051 * A {@link Schedulable} job for detecting contents with "Live" label but which are not in workspace live or not indexed and synchronizing/indexing them. 052 */ 053public class SynchronizeMissingLiveContentsSchedulable extends AbstractStaticSchedulable 054{ 055 /** The ametys object resolver. */ 056 protected AmetysObjectResolver _resolver; 057 /** The repository */ 058 protected Repository _repository; 059 /** The synchronize component*/ 060 protected SynchronizeComponent _synchronizeComponent; 061 /** The Solr client provider */ 062 protected SolrClientProvider _solrClientProvider; 063 /** The Solr indexer */ 064 protected SolrIndexer _solrIndexer; 065 066 @Override 067 public void service(ServiceManager manager) throws ServiceException 068 { 069 super.service(manager); 070 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 071 _repository = (Repository) manager.lookup(Repository.class.getName()); 072 _synchronizeComponent = (SynchronizeComponent) manager.lookup(SynchronizeComponent.ROLE); 073 _solrClientProvider = (SolrClientProvider) manager.lookup(SolrClientProvider.ROLE); 074 _solrIndexer = (SolrIndexer) manager.lookup(SolrIndexer.ROLE); 075 } 076 077 public void execute(JobExecutionContext context, ContainerProgressionTracker progressionTracker) throws Exception 078 { 079 Request request = ContextHelper.getRequest(_context); 080 081 // Retrieve the current workspace. 082 String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 083 Session liveSession = null; 084 085 int errors = 0; 086 int nbOfSynchronized = 0; 087 int nbOfIndexed = 0; 088 try 089 { 090 // Force the workspace. 091 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, RepositoryConstants.DEFAULT_WORKSPACE); 092 093 // Prepare live session to synchronize missing contents 094 liveSession = _repository.login(WebConstants.LIVE_WORKSPACE); 095 096 // Prepare Solr elements to index missing contents 097 String collectionName = _solrClientProvider.getCollectionName(WebConstants.LIVE_WORKSPACE); 098 SolrClient readClient = _solrClientProvider.getReadClient(); 099 100 // Get contents 101 String query = ContentQueryHelper.getContentXPathQuery(null); 102 AmetysObjectIterable<Content> contents = _resolver.query(query); 103 104 // Initialize the progression tracker size 105 progressionTracker.setSize(contents.getSize(), new I18nizableText("plugin.web", "PLUGINS_WEB_SCHEDULER_SYNCHRONIZE_MISSING_LIVE_CONTENTS_UNIQUE_STEP_LABEL")); 106 107 // Iterate on contents 108 for (Content content : contents) 109 { 110 try 111 { 112 // Check if the content has a "Live" label 113 if (content instanceof DefaultContent defaultContent) 114 { 115 String[] allLabels = defaultContent.getAllLabels(); 116 117 if (ArrayUtils.contains(allLabels, CmsConstants.LIVE_LABEL)) 118 { 119 // Synchronize the content in the "live" workspace if it is missing 120 if (_trySynchronizingMissingLiveContent(defaultContent, liveSession)) 121 { 122 nbOfSynchronized++; 123 } 124 125 // Index the content in the "live" core if it is missing 126 if (_tryIndexingMissingLiveContent(defaultContent, collectionName, readClient)) 127 { 128 nbOfIndexed++; 129 } 130 } 131 } 132 } 133 catch (AmetysRepositoryException e) 134 { 135 getLogger().error("Failed to check content '{}' ({})", content.getTitle(), content.getId(), e); 136 } 137 catch (Exception e) 138 { 139 getLogger().error("Failed to synchronize or index content '{}' ({})", content.getTitle(), content.getId(), e); 140 errors++; 141 } 142 143 progressionTracker.increment(); 144 } 145 } 146 finally 147 { 148 if (liveSession != null) 149 { 150 liveSession.logout(); 151 } 152 153 // Restore context 154 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp); 155 156 getLogger().info("Number of contents synchronized: {}.", nbOfSynchronized); 157 getLogger().info("Number of contents indexed: {}", nbOfIndexed); 158 159 if (errors > 0) 160 { 161 throw new Exception(errors + " contents could not be checked or were not synchronized or indexed"); 162 } 163 } 164 } 165 166 private boolean _trySynchronizingMissingLiveContent(DefaultContent defaultContent, Session liveSession) throws RepositoryException 167 { 168 try 169 { 170 Node node = defaultContent.getNode(); 171 liveSession.getNodeByIdentifier(node.getIdentifier()); 172 } 173 catch (ItemNotFoundException e) 174 { 175 // If there is a Live label but no corresponding content in live workspace, repair the content by synchronizing it 176 _synchronizeComponent.synchronizeContent(defaultContent, liveSession); 177 liveSession.save(); 178 179 if (getLogger().isDebugEnabled()) 180 { 181 getLogger().debug("The content '{}' ({}) was synchronized", defaultContent.getTitle(), defaultContent.getId()); 182 } 183 184 return true; 185 } 186 187 return false; 188 } 189 190 private boolean _tryIndexingMissingLiveContent(DefaultContent defaultContent, String collectionName, SolrClient readClient) throws Exception 191 { 192 if (readClient.getById(collectionName, defaultContent.getId()) == null) 193 { 194 // If there is a Live label but no corresponding content indexed, repair the content by indexing it 195 _solrIndexer.indexContent(defaultContent.getId(), WebConstants.LIVE_WORKSPACE, true); 196 197 if (getLogger().isDebugEnabled()) 198 { 199 getLogger().debug("The content '{}' ({}) was synchronized", defaultContent.getTitle(), defaultContent.getId()); 200 } 201 202 return true; 203 } 204 205 return false; 206 } 207}