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