/* * Copyright 2013 Anyware Services * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.ametys.cms.repository; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.avalon.framework.parameters.Parameters; import org.apache.avalon.framework.service.ServiceException; import org.apache.avalon.framework.service.ServiceManager; import org.apache.cocoon.acting.ServiceableAction; import org.apache.cocoon.environment.ObjectModelHelper; import org.apache.cocoon.environment.Redirector; import org.apache.cocoon.environment.Request; import org.apache.cocoon.environment.SourceResolver; import org.apache.cocoon.servlet.multipart.Part; import org.apache.cocoon.servlet.multipart.PartOnDisk; import org.apache.cocoon.servlet.multipart.RejectedPart; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.input.BOMInputStream; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.tika.parser.txt.CharsetDetector; import org.supercsv.io.CsvListReader; import org.supercsv.io.ICsvListReader; import org.supercsv.prefs.CsvPreference; import org.ametys.cms.contenttype.ContentType; import org.ametys.cms.contenttype.ContentTypeExtensionPoint; import org.ametys.cms.contenttype.ContentTypesHelper; import org.ametys.cms.contenttype.MetadataDefinition; import org.ametys.cms.contenttype.MetadataType; import org.ametys.cms.workflow.AbstractContentWorkflowComponent; import org.ametys.cms.workflow.ContentWorkflowHelper; import org.ametys.cms.workflow.EditContentFunction; import org.ametys.core.cocoon.JSonReader; import org.ametys.core.util.JSONUtils; import org.ametys.plugins.repository.AmetysRepositoryException; import org.ametys.plugins.workflow.AbstractWorkflowComponent; import com.opensymphony.workflow.WorkflowException; /** * Imports simple contents from a CSV file * */ public class ImportSimpleContentsAction extends ServiceableAction { private static final String[] _ALLOWED_EXTENSIONS = new String[] {"txt", "csv"}; private ContentTypeExtensionPoint _contentTypeEP; private ContentWorkflowHelper _contentWorkflowHelper; private ContentTypeExtensionPoint _cTypeEP; private ContentTypesHelper _contentTypeHelper; private JSONUtils _jsonUtils; @Override public void service(ServiceManager smanager) throws ServiceException { super.service(smanager); _contentTypeEP = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE); _contentTypeHelper = (ContentTypesHelper) smanager.lookup(ContentTypesHelper.ROLE); _contentWorkflowHelper = (ContentWorkflowHelper) smanager.lookup(ContentWorkflowHelper.ROLE); _cTypeEP = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE); _jsonUtils = (JSONUtils) smanager.lookup(JSONUtils.ROLE); } @Override public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception { Map<String, Object> result = new HashMap<>(); Request request = ObjectModelHelper.getRequest(objectModel); String cTypeId = request.getParameter("contentType"); String lang = request.getParameter("contentLanguage"); ContentType cType = _contentTypeEP.getExtension(cTypeId); if (!cType.isSimple()) { result.put("error", "invalid-content-type"); request.setAttribute(JSonReader.OBJECT_TO_READ, result); return EMPTY_MAP; } Part part = (Part) request.get("importFile"); if (part instanceof RejectedPart) { result.put("error", "rejected-file"); request.setAttribute(JSonReader.OBJECT_TO_READ, result); return EMPTY_MAP; } PartOnDisk uploadedFilePart = (PartOnDisk) part; File uploadedFile = (uploadedFilePart != null) ? uploadedFilePart.getFile() : null; String filename = (uploadedFilePart != null) ? uploadedFilePart.getFileName().toLowerCase() : null; if (!FilenameUtils.isExtension(filename, _ALLOWED_EXTENSIONS)) { result.put("error", "invalid-extension"); request.setAttribute(JSonReader.OBJECT_TO_READ, result); return EMPTY_MAP; } String workflowName = request.getParameter("workflowName"); int initWorkflowActionId = Integer.valueOf(request.getParameter("initWorkflowActionId")); int workflowEditActionId = Integer.valueOf(request.getParameter("editWorkflowActionId")); try { List<String> contentIds = _createSimpleContents(cTypeId, lang, workflowName, initWorkflowActionId, workflowEditActionId, uploadedFile); result.put("contentIds", contentIds); result.put("success", "true"); } catch (Exception e) { result.put("error", "true"); result.put("errorMessage", e.getMessage()); getLogger().error("Unable to create simple contents.", e); } request.setAttribute(JSonReader.OBJECT_TO_READ, result); return EMPTY_MAP; } private List<String> _createSimpleContents (String contentTypeId, String lang, String workflowName, int initActionId, int editActionId, File uploadedFile) throws IOException, WorkflowException { List<String> contentIds = new ArrayList<>(); try (ICsvListReader csvReader = new CsvListReader(_getReader(new BOMInputStream(new FileInputStream(uploadedFile))), CsvPreference.EXCEL_NORTH_EUROPE_PREFERENCE)) { String[] metadataNames = csvReader.getHeader(true); // Remove spaces and tabs before and after the metadata name for (int i = 0; i < metadataNames.length; i++) { metadataNames[i] = metadataNames[i].trim(); } int titleIndex = ArrayUtils.indexOf(metadataNames, "title"); titleIndex = titleIndex != -1 ? titleIndex : 0; List<String> parts = new ArrayList<>(); while ((parts = csvReader.read()) != null) { Content content = _createSimpleContent(contentTypeId, lang, parts.get(titleIndex).trim(), workflowName, initActionId); _editSimpleContent(content, metadataNames, parts, editActionId, lang); contentIds.add(content.getId()); } } // FIXME catch WorkflowException and remove all created contents? return contentIds; } /** * Get a reader on the data stream that detects the charset. * @param in the data stream. * @return the reader with the correct character set. */ protected Reader _getReader(InputStream in) { // Use Tika/ICU to detect the file charset. BufferedInputStream buffIs = new BufferedInputStream(in); CharsetDetector detector = new CharsetDetector(); return detector.getReader(buffIs, Charset.defaultCharset().name()); } private Map<String, Object> _editSimpleContent (Content content, String[] metadataNames, List<String> values, int actionId, String defaultLang) throws AmetysRepositoryException, WorkflowException { Map<String, String> jsValues = new HashMap<>(); for (int i = 0; i < metadataNames.length; i++) { if (!metadataNames[i].equals("title") && values.size() > i) { MetadataDefinition metadataDef = _contentTypeHelper.getMetadataDefinition(metadataNames[i], content); if (metadataDef.getType() == MetadataType.MULTILINGUAL_STRING) { Map<String, String> rawValues = new HashMap<>(); rawValues.put(defaultLang, StringUtils.trim(values.get(i))); jsValues.put(EditContentFunction.FORM_ELEMENTS_PREFIX + metadataNames[i], _jsonUtils.convertObjectToJson(rawValues)); } else { jsValues.put(EditContentFunction.FORM_ELEMENTS_PREFIX + metadataNames[i], StringUtils.trim(values.get(i))); } } } Map<String, Object> contextParameters = new HashMap<>(); contextParameters.put("quit", true); contextParameters.put("values", jsValues); contextParameters.put(EditContentFunction.METADATA_SET_PARAM, null); Map<String, Object> inputs = new HashMap<>(); inputs.put(AbstractWorkflowComponent.CONTEXT_PARAMETERS_KEY, contextParameters); Map<String, Object> result = _contentWorkflowHelper.doAction((WorkflowAwareContent) content, actionId, inputs); return result; } private Content _createSimpleContent(String cTypeId, String lang, String contentTitle, String workflowName, int actionId) throws WorkflowException { ContentType cType = _cTypeEP.getExtension(cTypeId); MetadataDefinition titleMetaDef = cType.getMetadataDefinition(DefaultContent.METADATA_TITLE); // CMS-9474 Import reference table : Title with only figures are not supported String contentName = contentTitle; if (!_startsWithAlpha(contentName)) { contentName = "content-" + contentName; } Map<String, Object> result = null; if (titleMetaDef.getType() == MetadataType.MULTILINGUAL_STRING) { Map<String, String> titleVariants = new HashMap<>(); titleVariants.put(lang, contentTitle); result = _contentWorkflowHelper.createContent(workflowName, actionId, contentName, titleVariants, new String[] {cTypeId}, new String[0]); } else { result = _contentWorkflowHelper.createContent(workflowName, actionId, contentName, contentTitle, new String[] {cTypeId}, new String[0], lang); } return (Content) result.get(AbstractContentWorkflowComponent.CONTENT_KEY); } private boolean _startsWithAlpha(String stringToTest) { return stringToTest.toLowerCase().matches("^[a-zàèìòùáéíóúýâêîôûãñõäëïöüÿçߨøåæœ].*$"); } }