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.plugins.contentio.csv; 017 018import java.io.IOException; 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.HashMap; 022import java.util.List; 023import java.util.Locale; 024import java.util.Map; 025import java.util.Optional; 026import java.util.Set; 027import java.util.function.Function; 028 029import org.apache.avalon.framework.component.Component; 030import org.apache.avalon.framework.service.ServiceException; 031import org.apache.avalon.framework.service.ServiceManager; 032import org.apache.avalon.framework.service.Serviceable; 033import org.apache.commons.lang3.StringUtils; 034import org.supercsv.io.ICsvListReader; 035import org.supercsv.util.Util; 036 037import org.ametys.cms.contenttype.ContentAttributeDefinition; 038import org.ametys.cms.contenttype.ContentType; 039import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 040import org.ametys.cms.data.ContentValue; 041import org.ametys.cms.data.type.AbstractMultilingualStringElementType; 042import org.ametys.cms.data.type.impl.MultilingualStringRepositoryElementType; 043import org.ametys.cms.repository.Content; 044import org.ametys.cms.repository.ContentQueryHelper; 045import org.ametys.cms.repository.ModifiableDefaultContent; 046import org.ametys.cms.repository.ModifiableWorkflowAwareContent; 047import org.ametys.cms.workflow.AbstractContentWorkflowComponent; 048import org.ametys.cms.workflow.ContentWorkflowHelper; 049import org.ametys.cms.workflow.CreateContentFunction; 050import org.ametys.core.util.I18nUtils; 051import org.ametys.plugins.contentio.in.ContentImportException; 052import org.ametys.plugins.repository.AmetysObjectIterable; 053import org.ametys.plugins.repository.AmetysObjectResolver; 054import org.ametys.plugins.repository.data.holder.group.impl.ModifiableModelAwareRepeater; 055import org.ametys.plugins.repository.data.holder.values.SynchronizableRepeater; 056import org.ametys.plugins.repository.data.holder.values.SynchronizableValue; 057import org.ametys.plugins.repository.data.holder.values.SynchronizableValue.Mode; 058import org.ametys.plugins.repository.data.type.ModelItemTypeConstants; 059import org.ametys.plugins.repository.metadata.MultilingualString; 060import org.ametys.plugins.repository.metadata.MultilingualStringHelper; 061import org.ametys.plugins.repository.query.expression.AndExpression; 062import org.ametys.plugins.repository.query.expression.Expression; 063import org.ametys.plugins.repository.query.expression.Expression.Operator; 064import org.ametys.plugins.repository.query.expression.ExpressionContext; 065import org.ametys.plugins.repository.query.expression.MultilingualStringExpression; 066import org.ametys.plugins.repository.query.expression.StringExpression; 067import org.ametys.runtime.model.ElementDefinition; 068import org.ametys.runtime.model.ModelItem; 069import org.ametys.runtime.model.ModelViewItemGroup; 070import org.ametys.runtime.model.View; 071import org.ametys.runtime.model.ViewElement; 072import org.ametys.runtime.model.ViewElementAccessor; 073import org.ametys.runtime.model.ViewItem; 074import org.ametys.runtime.model.ViewItemAccessor; 075import org.ametys.runtime.model.type.ElementType; 076import org.ametys.runtime.plugin.component.AbstractLogEnabled; 077 078import com.opensymphony.workflow.WorkflowException; 079 080/** 081 * Import contents from an uploaded CSV file. 082 */ 083public class CSVImporter extends AbstractLogEnabled implements Component, Serviceable 084{ 085 /** Avalon Role */ 086 public static final String ROLE = CSVImporter.class.getName(); 087 088 private ContentWorkflowHelper _contentWorkflowHelper; 089 090 private ContentTypeExtensionPoint _contentTypeEP; 091 092 private AmetysObjectResolver _resolver; 093 094 private I18nUtils _i18nUtils; 095 096 @Override 097 public void service(ServiceManager smanager) throws ServiceException 098 { 099 _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE); 100 _contentTypeEP = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE); 101 _contentWorkflowHelper = (ContentWorkflowHelper) smanager.lookup(ContentWorkflowHelper.ROLE); 102 _i18nUtils = (I18nUtils) smanager.lookup(I18nUtils.ROLE); 103 } 104 105 /** 106 * Extract contents from CSV file 107 * @param mapping mapping of content attributes and CSV file header 108 * @param view View of importing content 109 * @param contentType content type to import 110 * @param listReader mapReader to parse CSV file 111 * @param createAction creation action id 112 * @param editAction edition action id 113 * @param workflowName workflow name 114 * @param language language of created content. 115 * @return list of created contents 116 * @throws IOException IOException while reading CSV 117 */ 118 public Map<String, Object> importContentsFromCSV(Map<String, Object> mapping, View view, ContentType contentType, ICsvListReader listReader, int createAction, int editAction, String workflowName, String language) throws IOException 119 { 120 List<String> contentIds = new ArrayList<>(); 121 String[] columns = listReader.getHeader(true); 122 int nbErrors = 0; 123 int nbWarnings = 0; 124 List<String> row = null; 125 126 while ((row = listReader.read()) != null) 127 { 128 try 129 { 130 131 if (listReader.length() != columns.length) 132 { 133 getLogger().error("Import from CSV file: content skipped because of invalid row: {}", row); 134 nbErrors++; 135 continue; 136 } 137 138 Map<String, String> rowMap = new HashMap<>(); 139 Util.filterListToMap(rowMap, columns, row); 140 List<ViewItem> errors = new ArrayList<>(); 141 Content content = _processContent(view, rowMap, contentType, mapping, createAction, editAction, workflowName, language, errors); 142 contentIds.add(content.getId()); 143 if (!errors.isEmpty()) 144 { 145 nbWarnings++; 146 } 147 } 148 catch (Exception e) 149 { 150 nbErrors++; 151 getLogger().error("Import from CSV file: error importing the content on line {}", listReader.getLineNumber(), e); 152 } 153 } 154 155 Map<String, Object> results = new HashMap<>(); 156 results.put("contentIds", contentIds); 157 results.put("nbErrors", nbErrors); 158 results.put("nbWarnings", nbWarnings); 159 return results; 160 } 161 162 private Content _processContent(View view, Map<String, String> row, ContentType contentType, Map<String, Object> mapping, int createAction, int editAction, String workflowName, String language, List<ViewItem> errors) throws Exception 163 { 164 @SuppressWarnings("unchecked") 165 List<String> attributeNames = (List<String>) mapping.get("id"); 166 @SuppressWarnings("unchecked") 167 Map<String, Object> mappingValues = (Map<String, Object>) mapping.get("values"); 168 Map<String, Object> values = new HashMap<>(); 169 ModifiableWorkflowAwareContent content = _getOrCreateContent(mappingValues, row, contentType, Optional.of(workflowName), createAction, language, view, attributeNames, Optional.empty()); 170 171 for (ViewItem viewItem : view.getViewItems()) 172 { 173 try 174 { 175 if (!_isId(viewItem, attributeNames)) 176 { 177 Object value = _getValue(content, viewItem, mappingValues, row, createAction, editAction, language, errors, ""); 178 if (value != null) 179 { 180 values.put(viewItem.getName(), value); 181 } 182 } 183 } 184 catch (Exception e) 185 { 186 errors.add(viewItem); 187 getLogger().error("Import from CSV file: error while trying to get values for view: {}", viewItem.getName(), e); 188 } 189 } 190 191 if (!values.isEmpty()) 192 { 193 if (content == null) 194 { 195 // Throw this exception only when values are filled, as an empty content should not trigger any warning 196 throw new ContentImportException("Can't create and fill content of content type '" + contentType.getId() + "' and following values '" + values + "' : at least one of those identifiers is null : " + attributeNames); 197 } 198 else 199 { 200 _editContent(editAction, values, content); 201 } 202 } 203 204 return content; 205 } 206 207 private void _editContent(int editAction, Map<String, Object> values, ModifiableWorkflowAwareContent content) throws WorkflowException 208 { 209 if (!values.isEmpty()) 210 { 211 _contentWorkflowHelper.editContent(content, values, editAction); 212 } 213 } 214 215 private boolean _isId(ViewItem viewItem, List<String> attributeNames) 216 { 217 if (viewItem instanceof ViewElement) 218 { 219 ViewElement viewElement = (ViewElement) viewItem; 220 ElementDefinition elementDefinition = viewElement.getDefinition(); 221 if (!(elementDefinition instanceof ContentAttributeDefinition)) 222 { 223 String elementName = elementDefinition.getName(); 224 return attributeNames.contains(elementName); 225 } 226 } 227 return false; 228 } 229 230 private Object _getValue(Content parentContent, ViewItem viewItem, Map<String, Object> mapping, Map<String, String> row, int createAction, int editAction, String language, List<ViewItem> errors, String prefix) throws Exception 231 { 232 if (viewItem instanceof ViewElement) 233 { 234 ViewElement viewElement = (ViewElement) viewItem; 235 ElementDefinition elementDefinition = viewElement.getDefinition(); 236 if (elementDefinition instanceof ContentAttributeDefinition) 237 { 238 return _getContentAttributeDefinitionValues(parentContent, viewItem, mapping, row, createAction, editAction, language, viewElement, elementDefinition, errors); 239 } 240 else 241 { 242 return _getAttributeDefinitionValues(parentContent, mapping, row, elementDefinition, language, prefix); 243 } 244 } 245 else if (viewItem instanceof ModelViewItemGroup) 246 { 247 ModelViewItemGroup modelViewItemGroup = (ModelViewItemGroup) viewItem; 248 List<ViewItem> children = modelViewItemGroup.getViewItems(); 249 @SuppressWarnings("unchecked") 250 Map<String, Object> nestedMap = (Map<String, Object>) mapping.get(viewItem.getName()); 251 @SuppressWarnings("unchecked") 252 Map<String, Object> nestedMapValues = (Map<String, Object>) (nestedMap.get("values")); 253 if (ModelItemTypeConstants.REPEATER_TYPE_ID.equals(modelViewItemGroup.getDefinition().getType().getId())) 254 { 255 return _getRepeaterValues(parentContent, modelViewItemGroup, row, createAction, editAction, language, children, nestedMap, errors, prefix); 256 } 257 else 258 { 259 return _getCompositeValues(parentContent, viewItem, row, createAction, editAction, language, children, nestedMapValues, errors, prefix); 260 } 261 } 262 else 263 { 264 errors.add(viewItem); 265 throw new RuntimeException("Import from CSV file: unsupported type of ViewItem for view: " + viewItem.getName()); 266 } 267 } 268 269 private Map<String, Object> _getCompositeValues(Content parentContent, ViewItem viewItem, Map<String, String> row, int createAction, int editAction, 270 String language, List<ViewItem> children, Map<String, Object> nestedMapValues, List<ViewItem> errors, String prefix) 271 { 272 Map<String, Object> compositeValues = new HashMap<>(); 273 for (ViewItem child : children) 274 { 275 try 276 { 277 compositeValues.put(child.getName(), _getValue(parentContent, child, nestedMapValues, row, createAction, editAction, language, errors, prefix + viewItem.getName() + ModelItem.ITEM_PATH_SEPARATOR)); 278 } 279 catch (Exception e) 280 { 281 errors.add(viewItem); 282 getLogger().error("Import from CSV file: error while trying to get values for view: {}", viewItem.getName(), e); 283 } 284 } 285 return compositeValues; 286 } 287 288 private SynchronizableRepeater _getRepeaterValues(Content parentContent, ModelViewItemGroup viewItem, Map<String, String> row, int createAction, int editAction, String language, 289 List<ViewItem> children, Map<String, Object> nestedMap, List<ViewItem> errors, String prefix) 290 { 291 @SuppressWarnings("unchecked") 292 Map<String, Object> mappingValues = (Map<String, Object>) (nestedMap.get("values")); 293 @SuppressWarnings("unchecked") 294 List<String> attributeNames = (List<String>) nestedMap.get("id"); 295 Map<String, Object> repeaterValues = new HashMap<>(); 296 List<Map<String, Object>> repeaterValuesList = new ArrayList<>(); 297 List<Integer> indexList = new ArrayList<>(); 298 299 if (_allAttributesFilled(mappingValues, attributeNames) && parentContent != null && parentContent.hasValue(prefix + viewItem.getName())) 300 { 301 ModifiableModelAwareRepeater repeater = parentContent.getValue(prefix + viewItem.getName()); 302 303 repeater.getEntries().forEach(entry -> 304 { 305 boolean allEquals = !attributeNames.isEmpty() && attributeNames.stream() 306 .allMatch(attributeName -> 307 { 308 String idValue = (String) mappingValues.get(attributeName); 309 ViewElement viewElement = (ViewElement) viewItem.getModelViewItem(attributeName); 310 ElementDefinition elementDefinition = viewElement.getDefinition(); 311 ElementType elementType = elementDefinition.getType(); 312 Object value = elementType.castValue(row.get(idValue)); 313 return value != null && value.equals(entry.getValue(attributeName)); 314 }); 315 316 if (allEquals) 317 { 318 indexList.add(entry.getPosition()); 319 } 320 }); 321 } 322 323 Integer rowIndex = indexList.size() > 0 ? indexList.get(0) : 1; 324 325 for (ViewItem child : children) 326 { 327 try 328 { 329 Object entryValues = _getValue(parentContent, child, mappingValues, row, createAction, editAction, language, errors, prefix + viewItem.getName() + "[" + rowIndex + "]" + ModelItem.ITEM_PATH_SEPARATOR); 330 if (entryValues != null) 331 { 332 repeaterValues.put(child.getName(), entryValues); 333 } 334 } 335 catch (Exception e) 336 { 337 errors.add(viewItem); 338 getLogger().error("Import from CSV file: error while trying to get values for view: {}", viewItem.getName(), e); 339 } 340 } 341 repeaterValuesList.add(repeaterValues); 342 343 344 if (indexList.size() == 1) 345 { 346 return SynchronizableRepeater.replace(repeaterValuesList, indexList); 347 } 348 // If several rows match the id, only replace the first but add a warning 349 else if (indexList.size() > 1) 350 { 351 errors.add(viewItem); 352 return SynchronizableRepeater.replace(repeaterValuesList, List.of(indexList.get(0))); 353 } 354 else 355 { 356 return SynchronizableRepeater.appendOrRemove(repeaterValuesList, Set.of()); 357 } 358 359 } 360 361 private Object _getContentAttributeDefinitionValues(Content parentContent, ViewItem viewItem, Map<String, Object> mapping, Map<String, String> row, 362 int createAction, int editAction, String language, ViewElement viewElement, ElementDefinition elementDefinition, List<ViewItem> errors) throws Exception 363 { 364 ViewElementAccessor viewElementAccessor = (ViewElementAccessor) viewElement; 365 ContentAttributeDefinition contentAttributeDefinition = (ContentAttributeDefinition) elementDefinition; 366 String contentTypeId = contentAttributeDefinition.getContentTypeId(); 367 ContentType contentType = _contentTypeEP.getExtension(contentTypeId); 368 @SuppressWarnings("unchecked") 369 Map<String, Object> nestedMap = (Map<String, Object>) mapping.get(viewItem.getName()); 370 @SuppressWarnings("unchecked") 371 Map<String, Object> mappingValues = (Map<String, Object>) (nestedMap.get("values")); 372 @SuppressWarnings("unchecked") 373 List<String> attributeNames = (List<String>) nestedMap.get("id"); 374 if (_allAttributesFilled(mappingValues, attributeNames)) 375 { 376 Map<String, Object> values = new HashMap<>(); 377 ModifiableWorkflowAwareContent attachedContent = _getOrCreateContent(mappingValues, row, contentType, Optional.empty(), createAction, language, viewElementAccessor, attributeNames, Optional.of(parentContent)); 378 for (ViewItem nestedViewItem : viewElementAccessor.getViewItems()) 379 { 380 try 381 { 382 if (!_isId(nestedViewItem, attributeNames)) 383 { 384 Object value = _getValue(attachedContent, nestedViewItem, mappingValues, row, createAction, editAction, language, errors, ""); 385 if (value != null) 386 { 387 values.put(nestedViewItem.getName(), value); 388 } 389 } 390 } 391 catch (Exception e) 392 { 393 errors.add(viewItem); 394 getLogger().error("Import from CSV file: error while trying to get values for view: {}", viewItem.getName(), e); 395 } 396 } 397 398 if (!values.isEmpty()) 399 { 400 if (attachedContent == null) 401 { 402 // Throw this exception only when values are filled, as an empty content should not trigger any warning 403 throw new ContentImportException("Can't create and fill content of content type '" + contentType.getId() + "' and following values '" + values + "' : at least one of those identifiers is null : " + attributeNames); 404 } 405 else 406 { 407 _editContent(editAction, values, attachedContent); 408 } 409 } 410 411 //If content is multiple, we keep the old value list and we check if content was already inside, and add it otherwise 412 if (contentAttributeDefinition.isMultiple() && attachedContent != null) 413 { 414 ContentValue[] multipleContents = parentContent.getValue(contentAttributeDefinition.getPath()); 415 // If there is no list or if it is empty, add it. Otherwise, we have to check if it is inside. 416 if (!parentContent.hasValue(contentAttributeDefinition.getPath()) || !_containsContent(attachedContent, multipleContents)) 417 { 418 SynchronizableValue syncValue = new SynchronizableValue(List.of(attachedContent)); 419 syncValue.setMode(Mode.APPEND); 420 return syncValue; 421 } 422 } 423 else 424 { 425 return attachedContent; 426 } 427 } 428 return null; 429 } 430 431 private boolean _containsContent(ModifiableWorkflowAwareContent attachedContent, ContentValue[] multipleContents) 432 { 433 return Arrays.stream(multipleContents) 434 .anyMatch(contentValue -> attachedContent.getId().equals(contentValue.getContentId())); 435 } 436 437 438 private Object _getAttributeDefinitionValues(Content parentContent, Map<String, Object> mapping, Map<String, String> row, ElementDefinition elementDefinition, String language, String prefix) 439 { 440 ElementType elementType = elementDefinition.getType(); 441 String elementName = elementDefinition.getName(); 442 String elementColumn = (String) mapping.get(elementName); 443 String valueAsString = row.get(elementColumn); 444 Object value; 445 if (elementType instanceof AbstractMultilingualStringElementType && !MultilingualStringHelper.matchesMultilingualStringPattern(valueAsString)) 446 { 447 MultilingualString multilingualString = new MultilingualString(); 448 multilingualString.add(new Locale(language), valueAsString); 449 value = multilingualString; 450 } 451 else 452 { 453 value = elementType.castValue(valueAsString); 454 } 455 if (elementDefinition.isMultiple()) 456 { 457 // Build path with index for repeaters. 458 String pathWithIndex = prefix + elementDefinition.getName(); 459 460 // If there is no list or if it is empty, add it. Otherwise, we have to check if it is inside. 461 // If there is no parentContent, still append as we want to fill the values map anyway. 462 if (parentContent == null || !parentContent.hasValue(pathWithIndex) || !_containsValue(value, parentContent.getValue(pathWithIndex))) 463 { 464 SynchronizableValue syncValue = new SynchronizableValue(value != null ? List.of(value) : List.of()); 465 syncValue.setMode(Mode.APPEND); 466 return syncValue; 467 } 468 } 469 else 470 { 471 return value; 472 } 473 return null; 474 } 475 476 private boolean _containsValue(Object value, Object[] multipleContents) 477 { 478 return Arrays.stream(multipleContents) 479 .anyMatch(valueFromContent -> valueFromContent.equals(value)); 480 } 481 482 private ModifiableWorkflowAwareContent _getOrCreateContent(Map<String, Object> mapping, Map<String, String> row, ContentType contentType, Optional<String> workflowName, int createAction, String language, ViewItemAccessor viewItemAccessor, List<String> attributeNames, Optional<Content> parentContent) throws ContentImportException, WorkflowException 483 { 484 List<Expression> idExpressions = new ArrayList<>(); 485 List<String> values = new ArrayList<>(); 486 487 for (String attributeName : attributeNames) 488 { 489 ViewElement viewElement = (ViewElement) viewItemAccessor.getModelViewItem(attributeName); 490 ElementDefinition elementDefinition = viewElement.getDefinition(); 491 String attributePath = (String) mapping.get(attributeName); 492 final String value = row.get(attributePath); 493 values.add(value); 494 495 if (value == null) 496 { 497 return null; 498 } 499 500 // Get content 501 if (elementDefinition.getType() instanceof MultilingualStringRepositoryElementType) 502 { 503 idExpressions.add(new MultilingualStringExpression(attributeName, Operator.EQ, value, language)); 504 } 505 else 506 { 507 idExpressions.add(new StringExpression(attributeName, Operator.EQ, value)); 508 } 509 } 510 511 idExpressions.add(_contentTypeEP.createHierarchicalCTExpression(contentType.getId())); 512 513 if (!contentType.isMultilingual()) 514 { 515 idExpressions.add(new StringExpression("language", Operator.EQ, language, ExpressionContext.newInstance().withInternal(true))); 516 } 517 518 Expression expression = new AndExpression(idExpressions.toArray(new Expression[idExpressions.size()])); 519 520 String xPathQuery = ContentQueryHelper.getContentXPathQuery(expression); 521 AmetysObjectIterable<ModifiableDefaultContent> matchingContents = _resolver.query(xPathQuery); 522 if (matchingContents.getSize() > 1) 523 { 524 throw new ContentImportException("More than one content found for type " + contentType.getLabel() + " with " 525 + attributeNames + " as identifier and " + values + " as value"); 526 } 527 else if (matchingContents.getSize() == 1) 528 { 529 return matchingContents.iterator().next(); 530 } 531 532 // Create content 533 534 if (contentType.isAbstract()) 535 { 536 throw new ContentImportException("Can not create content for type " + contentType.getLabel() + " with " 537 + attributeNames + " as identifier and " + values + " as value, the content type is abstract"); 538 } 539 540 Map<String, Object> result; 541 String title; 542 if (mapping.containsKey("title")) 543 { 544 title = row.get(mapping.get("title")); 545 } 546 else 547 { 548 title = _i18nUtils.translate(contentType.getDefaultTitle(), language); 549 } 550 551 552 String finalWorkflowName = workflowName.or(contentType::getDefaultWorkflowName) 553 .orElseThrow(() -> new ContentImportException("No workflow specified for content type " + contentType.getLabel() + " with " 554 + attributeNames + " as identifier and " + values + " as value")); 555 556 Map<String, Object> inputs = new HashMap<>(); 557 inputs.put(CreateContentFunction.INITIAL_VALUE_SUPPLIER, new Function<List<String>, Object>() 558 { 559 public Object apply(List<String> keys) 560 { 561 // Browse the mapping to find the column related to the attribute 562 Object nestedValue = mapping; 563 for (String key : keys) 564 { 565 nestedValue = ((Map) nestedValue).get(key); 566 // If nestedValue is null, the attribute is absent from the map, no value can be found 567 if (nestedValue == null) 568 { 569 return null; 570 } 571 // If nestedValue is a map, the key is a complex element such a content or a composite, 572 // we need to keep browsing the map to find the column 573 if (nestedValue instanceof Map) 574 { 575 nestedValue = ((Map) nestedValue).get("values"); 576 } 577 } 578 579 // Get the value of the attribute for the current row 580 return row.get(nestedValue); 581 } 582 }); 583 584 parentContent.ifPresent(content -> inputs.put(CreateContentFunction.PARENT_CONTEXT_VALUE, content.getId())); 585 586 // CONTENTIO-253 To avoid issue with title starting with a non letter character, we prefix the name with the contentTypeId 587 String prefix = StringUtils.substringAfterLast(contentType.getId(), ".").toLowerCase(); 588 String contentName = prefix + "-" + title; 589 590 if (contentType.isMultilingual()) 591 { 592 inputs.put(CreateContentFunction.CONTENT_LANGUAGE_KEY, language); 593 result = _contentWorkflowHelper.createContent(finalWorkflowName, createAction, contentName, Map.of(language, title), new String[] {contentType.getId()}, null, null, null, inputs); 594 } 595 else 596 { 597 result = _contentWorkflowHelper.createContent(finalWorkflowName, createAction, contentName, title, new String[] {contentType.getId()}, null, language, null, null, inputs); 598 } 599 600 ModifiableWorkflowAwareContent content = (ModifiableWorkflowAwareContent) result.get(AbstractContentWorkflowComponent.CONTENT_KEY); 601 602 603 for (String attributeName : attributeNames) 604 { 605 ViewElement viewElement = (ViewElement) viewItemAccessor.getModelViewItem(attributeName); 606 ElementDefinition elementDefinition = viewElement.getDefinition(); 607 String attributePath = (String) mapping.get(attributeName); 608 final String value = row.get(attributePath); 609 if (org.ametys.cms.data.type.ModelItemTypeConstants.MULTILINGUAL_STRING_ELEMENT_TYPE_ID.equals(elementDefinition.getType().getId())) 610 { 611 MultilingualString multilingualString = new MultilingualString(); 612 multilingualString.add(new Locale(language), value); 613 content.setValue(attributeName, multilingualString); 614 } 615 else 616 { 617 content.setValue(attributeName, elementDefinition.getType().castValue(value)); 618 } 619 } 620 return content; 621 } 622 623 private boolean _allAttributesFilled(Map<String, Object> mappingValues, List<String> attributeNames) 624 { 625 return mappingValues.entrySet() 626 .stream() 627 .filter(entry -> attributeNames.contains(entry.getKey())) 628 .allMatch(entry -> entry.getValue() != null); 629 } 630}