001/*
002 *  Copyright 2019 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.skill;
017
018import java.io.File;
019import java.io.FileInputStream;
020import java.io.IOException;
021import java.io.InputStreamReader;
022import java.util.ArrayList;
023import java.util.HashMap;
024import java.util.List;
025import java.util.Map;
026
027import org.apache.avalon.framework.component.Component;
028import org.apache.avalon.framework.service.ServiceException;
029import org.apache.avalon.framework.service.ServiceManager;
030import org.apache.avalon.framework.service.Serviceable;
031import org.apache.commons.lang.ArrayUtils;
032import org.apache.commons.lang3.StringUtils;
033import org.supercsv.io.CsvListReader;
034import org.supercsv.prefs.CsvPreference;
035
036import org.ametys.cms.content.indexing.solr.observation.ObserverHelper;
037import org.ametys.cms.repository.ContentQueryHelper;
038import org.ametys.cms.repository.ContentTypeExpression;
039import org.ametys.cms.repository.ModifiableContent;
040import org.ametys.cms.repository.WorkflowAwareContent;
041import org.ametys.cms.workflow.AbstractContentWorkflowComponent;
042import org.ametys.cms.workflow.ContentWorkflowHelper;
043import org.ametys.plugins.repository.AmetysObjectIterable;
044import org.ametys.plugins.repository.AmetysObjectIterator;
045import org.ametys.plugins.repository.AmetysObjectResolver;
046import org.ametys.plugins.repository.AmetysRepositoryException;
047import org.ametys.plugins.repository.query.expression.AndExpression;
048import org.ametys.plugins.repository.query.expression.Expression.Operator;
049import org.ametys.plugins.repository.query.expression.StringExpression;
050import org.ametys.runtime.plugin.component.AbstractLogEnabled;
051
052import com.opensymphony.workflow.WorkflowException;
053
054/**
055 * Create skills component
056 */
057public class CreateSkillsComponent extends AbstractLogEnabled implements Serviceable, Component
058{
059    /** The avalon role. */
060    public static final String ROLE = CreateSkillsComponent.class.getName();
061    
062    /** The skill content type id */
063    public static final String SKILL_CONTENT_TYPE = "odf-enumeration.Skill";
064    
065    /** The metadata name of the required skill sets */
066    public static final String REQUIRED_SKILL_SETS_METADATA_NAME = "requiredSkillSets";
067    
068    /** The metadata name of the acquired skill sets */
069    public static final String ACQUIRED_SKILL_SETS_METADATA_NAME = "acquiredSkillSets";
070    
071    /** The metadata name of the skills */
072    public static final String SKILLS_METADATA_NAME = "skills";
073    
074    /** The skills other names metadata name */
075    public static final String SKILL_OTHER_NAMES_METADATA_NAME = "otherNames";
076    
077    /** The content workflow helper */
078    protected ContentWorkflowHelper _contentWorkflowHelper;
079
080    /** The ametys object resolver */
081    protected AmetysObjectResolver _resolver;
082    
083    public void service(ServiceManager manager) throws ServiceException
084    {
085        _contentWorkflowHelper = (ContentWorkflowHelper) manager.lookup(ContentWorkflowHelper.ROLE);
086        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
087    }
088
089    /**
090     * Create all skills from ESCO file
091     * @param skillsCSVFilePath the skills CSV file path
092     */
093    public void createSkillsFromESCOFileCSV(String skillsCSVFilePath)
094    {
095        ObserverHelper.suspendObservationForIndexation();
096        
097        for (Skill skill : _getSkillsFromCSVFile(skillsCSVFilePath))
098        {
099            try
100            {
101                _createSkillTableRef(skill);
102            }
103            catch (AmetysRepositoryException | WorkflowException e) 
104            {
105                getLogger().warn("An error occurred creating skill with label {}", skill.getLabel(), e);
106            }
107        }
108        
109        ObserverHelper.restartObservationForIndexation();
110    }
111    
112    /**
113     * Get the list of skills from the csv file
114     * @param skillsCSVFilePath the skills CSV file path
115     * @return the list of skills
116     */
117    protected List<Skill> _getSkillsFromCSVFile(String skillsCSVFilePath)
118    {
119        List<Skill> skills = new ArrayList<>();
120        try (CsvListReader listReader = new CsvListReader(new InputStreamReader(new FileInputStream(new File(skillsCSVFilePath)), "UTF-8"), CsvPreference.STANDARD_PREFERENCE))
121        {
122            listReader.getHeader(true); //Skip header
123
124            List<String> read = listReader.read();
125            while (read != null)
126            {
127                String conceptUri = read.get(1); // Uri
128                String label = read.get(4); // Get label
129                if (StringUtils.isNotBlank(label))
130                {
131                    String otherNamesAsString = read.get(5); // Get other names
132                    String[] otherNames = StringUtils.isNotBlank(otherNamesAsString) ? StringUtils.split(otherNamesAsString, "\n") : ArrayUtils.EMPTY_STRING_ARRAY;
133                    skills.add(new Skill(label, otherNames, conceptUri));
134                }
135                read = listReader.read();
136            }
137        }
138        catch (IOException e)
139        {
140            getLogger().warn("An error occurred parsing file {}", skillsCSVFilePath, e);
141        }
142        
143        getLogger().info("Find {} skills into file {}", skills.size(), skillsCSVFilePath);
144        
145        return skills;
146    }
147    
148    /**
149     * Create a skill table ref content from the skill object
150     * @param skill the skill object
151     * @throws AmetysRepositoryException if a repository error occurred
152     * @throws WorkflowException if a workflow error occurred
153     */
154    protected void _createSkillTableRef(Skill skill) throws AmetysRepositoryException, WorkflowException
155    {
156        String uri = skill.getConceptUri();
157        String titleFR = skill.getLabel();
158        String[] otherNames = skill.getOtherNames();
159        
160        ContentTypeExpression cTypeExpr = new ContentTypeExpression(Operator.EQ, SKILL_CONTENT_TYPE);
161        StringExpression codeExpr = new StringExpression("code", Operator.EQ, uri);
162        
163        String xpathQuery = ContentQueryHelper.getContentXPathQuery(new AndExpression(cTypeExpr, codeExpr));
164        AmetysObjectIterable<ModifiableContent> contents = _resolver.query(xpathQuery);
165        AmetysObjectIterator<ModifiableContent> it = contents.iterator();
166        
167        if (!it.hasNext())
168        {
169            Map<String, String> titleVariants = new HashMap<>();
170            titleVariants.put("fr", titleFR);
171            
172            Map<String, Object> result = _contentWorkflowHelper.createContent("reference-table", 1, titleFR, titleVariants, new String[] {SKILL_CONTENT_TYPE}, new String[0]);
173            ModifiableContent content = (ModifiableContent) result.get(AbstractContentWorkflowComponent.CONTENT_KEY);
174            
175            content.setValue("code", uri);
176            
177            if (otherNames.length > 0)
178            {
179                content.setValue(SKILL_OTHER_NAMES_METADATA_NAME, otherNames);
180            }
181            
182            content.saveChanges();
183            _contentWorkflowHelper.doAction((WorkflowAwareContent) content, 22);
184            
185            getLogger().info("Skill's content \"{}\" ({}) was successfully created", titleFR, content.getId());
186        }
187    }
188    
189    private static class Skill
190    {
191        private String _label;
192        private String[] _otherNames;
193        private String _conceptUri;
194        
195        public Skill(String label, String[] otherNames, String conceptUri)
196        {
197            _label = label;
198            _otherNames = otherNames;
199            _conceptUri = conceptUri;
200        }
201        
202        public String getLabel()
203        {
204            return _label;
205        }
206
207        public String[] getOtherNames()
208        {
209            return _otherNames;
210        }
211        
212        public String getConceptUri()
213        {
214            return _conceptUri;
215        }
216    }
217}