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}