001/* 002 * Copyright 2020 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.odf.schedulable; 017 018import java.io.IOException; 019import java.util.Collections; 020import java.util.Set; 021import java.util.stream.Collectors; 022 023import org.apache.avalon.framework.service.ServiceException; 024import org.apache.avalon.framework.service.ServiceManager; 025import org.apache.cocoon.components.ContextHelper; 026import org.apache.cocoon.environment.Request; 027import org.apache.commons.lang3.StringUtils; 028import org.apache.solr.client.solrj.SolrClient; 029import org.apache.solr.client.solrj.SolrServerException; 030import org.quartz.JobDataMap; 031import org.quartz.JobExecutionContext; 032 033import org.ametys.cms.content.indexing.solr.SolrIndexer; 034import org.ametys.cms.repository.Content; 035import org.ametys.cms.search.content.ContentSearcherFactory; 036import org.ametys.cms.search.query.Query.Operator; 037import org.ametys.cms.search.query.StringQuery; 038import org.ametys.cms.search.solr.SolrClientProvider; 039import org.ametys.core.schedule.progression.ContainerProgressionTracker; 040import org.ametys.odf.ProgramItem; 041import org.ametys.odf.catalog.CatalogsManager; 042import org.ametys.odf.program.ProgramFactory; 043import org.ametys.odf.program.SubProgramFactory; 044import org.ametys.plugins.core.impl.schedule.AbstractStaticSchedulable; 045import org.ametys.plugins.core.schedule.Scheduler; 046import org.ametys.plugins.repository.RepositoryConstants; 047import org.ametys.plugins.repository.provider.RequestAttributeWorkspaceSelector; 048 049/** 050 * Scheduler to compute the acquired skills on subprogram/programs from skills hold by their courses. 051 */ 052public class ComputeProgramSkillsSchedulable extends AbstractStaticSchedulable 053{ 054 /** The key for the catalog */ 055 public static final String JOBDATAMAP_CATALOG_KEY = "catalog"; 056 057 /** The catalog manager */ 058 protected CatalogsManager _catalogsManager; 059 /** The solr indexer */ 060 protected SolrIndexer _solrIndexer; 061 /** The provider for solr client */ 062 protected SolrClientProvider _solrClientProvider; 063 /** The content searcher */ 064 protected ContentSearcherFactory _contentSearcherFactory; 065 066 @Override 067 public void service(ServiceManager manager) throws ServiceException 068 { 069 super.service(manager); 070 _catalogsManager = (CatalogsManager) manager.lookup(CatalogsManager.ROLE); 071 _solrIndexer = (SolrIndexer) manager.lookup(SolrIndexer.ROLE); 072 _solrClientProvider = (SolrClientProvider) manager.lookup(SolrClientProvider.ROLE); 073 _contentSearcherFactory = (ContentSearcherFactory) manager.lookup(ContentSearcherFactory.ROLE); 074 } 075 076 @Override 077 public void execute(JobExecutionContext context, ContainerProgressionTracker progressionTracker) throws Exception 078 { 079 doReindex(context, RepositoryConstants.DEFAULT_WORKSPACE); 080 } 081 082 /** 083 * Find and reindex contents for a given workspace and catalog 084 * @param context The execution context 085 * @param workspaceName the workspace's name 086 */ 087 protected void doReindex(JobExecutionContext context, String workspaceName) 088 { 089 long time_0 = System.currentTimeMillis(); 090 091 JobDataMap jobDataMap = context.getJobDetail().getJobDataMap(); 092 String catalogName = jobDataMap.getString(Scheduler.PARAM_VALUES_PREFIX + JOBDATAMAP_CATALOG_KEY); 093 094 Set<String> contentIds = getContentIds(catalogName, workspaceName); 095 indexContents(contentIds, workspaceName); 096 097 getLogger().debug("Successfully reindex {} programs and subprograms for workspace '{}' and catalog '{}' in {} ms", contentIds.size(), workspaceName, catalogName, System.currentTimeMillis() - time_0); 098 } 099 100 /** 101 * Get the id of contents to re-index 102 * @param catalogName The catalog's name. Can be empty or null to get contents of default catalog 103 * @param workspaceName the workspace's name 104 * @return the id of contents to re-index 105 */ 106 protected Set<String> getContentIds(String catalogName, String workspaceName) 107 { 108 Request request = ContextHelper.getRequest(_context); 109 String currentWorkspace = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 110 111 try 112 { 113 // Force workspace 114 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, workspaceName); 115 116 String searchCatalogName = catalogName; 117 if (StringUtils.isEmpty(searchCatalogName)) 118 { 119 searchCatalogName = _catalogsManager.getDefaultCatalogName(); 120 } 121 122 Set<String> contentIds = _contentSearcherFactory 123 .create(ProgramFactory.PROGRAM_CONTENT_TYPE, SubProgramFactory.SUBPROGRAM_CONTENT_TYPE) 124 .setCheckRights(false) 125 .search(new StringQuery(ProgramItem.CATALOG, Operator.EQ, searchCatalogName, null)) 126 .stream() 127 .map(Content::getId) 128 .collect(Collectors.toSet()); 129 130 getLogger().debug("Found {} contents to reindex for workspace '{}' and catalog '{}'", contentIds.size(), workspaceName, searchCatalogName); 131 132 return contentIds; 133 } 134 catch (Exception e) 135 { 136 getLogger().error("Unable to get content ids to reindex for updating skills", e); 137 return Collections.EMPTY_SET; 138 } 139 finally 140 { 141 // Restore current workspace 142 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWorkspace); 143 } 144 } 145 146 /** 147 * Index the given contents for a given workspace 148 * @param contentIds the id of contents to index 149 * @param workspaceName the workspace's name 150 */ 151 protected void indexContents(Set<String> contentIds, String workspaceName) 152 { 153 SolrClient solrClient = _solrClientProvider.getUpdateClient(workspaceName, false); 154 155 contentIds.stream().forEach(id -> 156 { 157 try 158 { 159 _solrIndexer.indexContent(id, workspaceName, false, solrClient); 160 } 161 catch (Exception e) 162 { 163 getLogger().error("Fail to re-index content with id '{}' after computing skills", id); 164 } 165 }); 166 167 try 168 { 169 // Commit all uncommited changes 170 _solrIndexer.commit(); 171 } 172 catch (IOException | SolrServerException e) 173 { 174 getLogger().error("Unable to commit changes into Solr index after computing skills", e); 175 } 176 } 177}