001/* 002 * Copyright 2017 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.odfsync.apogee.scc; 017 018import java.io.ByteArrayInputStream; 019import java.io.File; 020import java.io.FileInputStream; 021import java.io.IOException; 022import java.io.InputStream; 023import java.math.BigDecimal; 024import java.sql.Clob; 025import java.sql.SQLException; 026import java.util.ArrayList; 027import java.util.Arrays; 028import java.util.Date; 029import java.util.HashMap; 030import java.util.HashSet; 031import java.util.LinkedHashMap; 032import java.util.LinkedHashSet; 033import java.util.LinkedList; 034import java.util.List; 035import java.util.Map; 036import java.util.Set; 037 038import org.apache.avalon.framework.configuration.Configuration; 039import org.apache.avalon.framework.configuration.ConfigurationException; 040import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder; 041import org.apache.avalon.framework.context.Context; 042import org.apache.avalon.framework.context.ContextException; 043import org.apache.avalon.framework.context.Contextualizable; 044import org.apache.avalon.framework.service.ServiceException; 045import org.apache.avalon.framework.service.ServiceManager; 046import org.apache.cocoon.Constants; 047import org.apache.cocoon.components.ContextHelper; 048import org.apache.cocoon.environment.Request; 049import org.apache.commons.io.IOUtils; 050import org.apache.commons.lang.StringUtils; 051import org.slf4j.Logger; 052 053import org.ametys.cms.content.external.ExternalizableMetadataHelper; 054import org.ametys.cms.content.external.ExternalizableMetadataProvider.ExternalizableMetadataStatus; 055import org.ametys.cms.contenttype.ContentType; 056import org.ametys.cms.contenttype.MetadataDefinition; 057import org.ametys.cms.contenttype.MetadataType; 058import org.ametys.cms.repository.ModifiableDefaultContent; 059import org.ametys.core.util.JSONUtils; 060import org.ametys.plugins.contentio.ContentImporterHelper; 061import org.ametys.plugins.contentio.synchronize.AbstractSimpleSynchronizableContentsCollection; 062import org.ametys.plugins.contentio.synchronize.SynchronizableContentsCollection; 063import org.ametys.plugins.odfsync.apogee.ApogeeDAO; 064import org.ametys.plugins.odfsync.apogee.scc.impl.OrgUnitSynchronizableContentsCollection; 065import org.ametys.plugins.repository.metadata.ModifiableCompositeMetadata; 066import org.ametys.plugins.repository.metadata.ModifiableRichText; 067import org.ametys.runtime.config.Config; 068import org.ametys.runtime.i18n.I18nizableText; 069 070import com.google.common.base.CharMatcher; 071import com.google.common.collect.ImmutableList; 072import com.google.common.collect.ImmutableMap; 073 074/** 075 * Abstract class for Apogee synchronization 076 */ 077public abstract class AbstractApogeeSynchronizableContentsCollection extends AbstractSimpleSynchronizableContentsCollection implements Contextualizable, ApogeeSynchronizableContentsCollection 078{ 079 /** 080 * Request attribute name to store handle contents during import or synchronization. 081 */ 082 public static final String HANDLE_CONTENTS = AbstractApogeeSynchronizableContentsCollection.class.getName() + "$handleContents"; 083 084 /** Name of parameter holding the field ID column */ 085 protected static final String __PARAM_ID_COLUMN = "idColumn"; 086 /** Name of parameter holding the fields mapping */ 087 protected static final String __PARAM_MAPPING = "mapping"; 088 /** Name of parameter into mapping holding the synchronized property */ 089 protected static final String __PARAM_MAPPING_SYNCHRO = "synchro"; 090 /** Name of parameter into mapping holding the path of metadata */ 091 protected static final String __PARAM_MAPPING_METADATA_REF = "metadata-ref"; 092 /** Name of parameter into mapping holding the remote attribute */ 093 protected static final String __PARAM_MAPPING_ATTRIBUTE = "attribute"; 094 /** Name of parameter holding the criteria */ 095 protected static final String __PARAM_CRITERIA = "criteria"; 096 /** Name of parameter into criteria holding a criterion */ 097 protected static final String __PARAM_CRITERIA_CRITERION = "criterion"; 098 /** Name of parameter into criterion holding the id */ 099 protected static final String __PARAM_CRITERIA_CRITERION_ID = "id"; 100 /** Name of parameter into criterion holding the label */ 101 protected static final String __PARAM_CRITERIA_CRITERION_LABEL = "label"; 102 /** Name of parameter into criterion holding the type */ 103 protected static final String __PARAM_CRITERIA_CRITERION_TYPE = "type"; 104 /** Name of paramter holding columns */ 105 protected static final String __PARAM_COLUMNS = "columns"; 106 /** Name of paramter into columns holding column */ 107 protected static final String __PARAM_COLUMNS_COLUMN = "column"; 108 109 /** Default language configured for ODF */ 110 protected String _odfLang; 111 112 /** Name of the Apogée column which contains the ID */ 113 protected String _idColumn; 114 115 /** Mapping between metadata and columns */ 116 protected Map<String, List<String>> _mapping; 117 118 /** External fields */ 119 protected Set<String> _extFields; 120 121 /** Synchronized fields */ 122 protected Set<String> _syncFields; 123 124 /** Synchronized fields */ 125 protected Set<String> _columns; 126 127 /** Synchronized fields */ 128 protected Set<ApogeeCriterion> _criteria; 129 130 /** Context */ 131 protected Context _context; 132 133 /** The DAO for remote DB Apogee */ 134 protected ApogeeDAO _apogeeDAO; 135 136 /** The JSON utils */ 137 protected JSONUtils _jsonUtils; 138 139 @Override 140 public void service(ServiceManager manager) throws ServiceException 141 { 142 super.service(manager); 143 _apogeeDAO = (ApogeeDAO) manager.lookup(ApogeeDAO.ROLE); 144 _jsonUtils = (JSONUtils) manager.lookup(JSONUtils.ROLE); 145 } 146 147 @Override 148 public void contextualize(Context context) throws ContextException 149 { 150 _context = context; 151 } 152 153 @Override 154 protected void configureDataSource(Configuration configuration) throws ConfigurationException 155 { 156 @SuppressWarnings("resource") 157 InputStream is = null; 158 _odfLang = Config.getInstance().getValue("odf.programs.lang"); 159 try 160 { 161 org.apache.cocoon.environment.Context ctx = (org.apache.cocoon.environment.Context) _context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT); 162 File apogeeMapping = new File(ctx.getRealPath("/WEB-INF/param/odf/apogee-mapping.xml")); 163 if (!apogeeMapping.isFile()) 164 { 165 is = getClass().getResourceAsStream("/org/ametys/plugins/odfsync/apogee/apogee-mapping.xml"); 166 } 167 else 168 { 169 is = new FileInputStream(apogeeMapping); 170 } 171 Configuration cfg = new DefaultConfigurationBuilder().build(is); 172 Configuration child = cfg.getChild(getMappingName()); 173 if (child != null) 174 { 175 _criteria = new LinkedHashSet<>(); 176 _columns = new LinkedHashSet<>(); 177 _idColumn = child.getChild(__PARAM_ID_COLUMN).getValue(); 178 _mapping = new HashMap<>(); 179 _extFields = new HashSet<>(); 180 _syncFields = new HashSet<>(); 181 String mappingAsString = child.getChild(__PARAM_MAPPING).getValue(); 182 _mapping.put(getIdField(), ImmutableList.of(getIdColumn())); 183 if (StringUtils.isNotEmpty(mappingAsString)) 184 { 185 List<Object> mappingAsList = _jsonUtils.convertJsonToList(mappingAsString); 186 for (Object object : mappingAsList) 187 { 188 @SuppressWarnings("unchecked") 189 Map<String, Object> field = (Map<String, Object>) object; 190 191 String metadataRef = (String) field.get(__PARAM_MAPPING_METADATA_REF); 192 193 String[] attributes = ((String) field.get(__PARAM_MAPPING_ATTRIBUTE)).split(","); 194 _mapping.put(metadataRef, Arrays.asList(attributes)); 195 196 boolean isSynchronized = field.containsKey(__PARAM_MAPPING_SYNCHRO) ? (Boolean) field.get(__PARAM_MAPPING_SYNCHRO) : false; 197 if (isSynchronized) 198 { 199 _syncFields.add(metadataRef); 200 } 201 else 202 { 203 _extFields.add(metadataRef); 204 } 205 } 206 } 207 208 Configuration[] criteria = child.getChild(__PARAM_CRITERIA).getChildren(__PARAM_CRITERIA_CRITERION); 209 for (Configuration criterion : criteria) 210 { 211 String id = criterion.getChild(__PARAM_CRITERIA_CRITERION_ID).getValue(); 212 I18nizableText label = _getCriterionLabel(criterion.getChild(__PARAM_CRITERIA_CRITERION_LABEL), id); 213 String type = criterion.getChild(__PARAM_CRITERIA_CRITERION_TYPE).getValue("STRING"); 214 215 _criteria.add(new ApogeeCriterion(id, label, type)); 216 } 217 218 Configuration[] columns = child.getChild(__PARAM_COLUMNS).getChildren(__PARAM_COLUMNS_COLUMN); 219 for (Configuration column : columns) 220 { 221 _columns.add(column.getValue()); 222 } 223 } 224 } 225 catch (Exception e) 226 { 227 throw new ConfigurationException("Error while parsing apogee-mapping.xml", e); 228 } 229 finally 230 { 231 IOUtils.closeQuietly(is); 232 } 233 } 234 235 private I18nizableText _getCriterionLabel(Configuration configuration, String defaultValue) 236 { 237 if (configuration.getAttributeAsBoolean("i18n", false)) 238 { 239 return new I18nizableText("plugin.odf-sync", configuration.getValue(defaultValue)); 240 } 241 else 242 { 243 return new I18nizableText(configuration.getValue(defaultValue)); 244 } 245 } 246 247 @Override 248 public List<ModifiableDefaultContent> populate(Logger logger) 249 { 250 boolean isRequestAttributeOwner = false; 251 252 Request request = ContextHelper.getRequest(_context); 253 if (request.getAttribute(HANDLE_CONTENTS) == null) 254 { 255 request.setAttribute(HANDLE_CONTENTS, new HashSet<String>()); 256 isRequestAttributeOwner = true; 257 } 258 259 List<ModifiableDefaultContent> populatedContents = super.populate(logger); 260 261 if (isRequestAttributeOwner) 262 { 263 request.removeAttribute(HANDLE_CONTENTS); 264 } 265 266 return populatedContents; 267 } 268 269 @Override 270 protected Map<String, Map<String, Object>> internalSearch(Map<String, Object> parameters, int offset, int limit, List<Object> sort, Logger logger) 271 { 272 Map<String, Object> searchParams = new HashMap<>(parameters); 273 if (offset > 0) 274 { 275 searchParams.put("__offset", offset); 276 } 277 if (limit < Integer.MAX_VALUE) 278 { 279 searchParams.put("__limit", offset + limit); 280 } 281 searchParams.put("__order", _getSort(sort)); 282 283 // We don't use session.selectMap which reorder data 284 List<Map<String, Object>> requestValues = _search(searchParams, logger); 285 286 String idColumn = getIdColumn(); 287 Map<String, Map<String, Object>> results = new LinkedHashMap<>(); 288 for (Map<String, Object> contentValues : requestValues) 289 { 290 results.put(contentValues.get(idColumn).toString(), contentValues); 291 } 292 293 for (Map<String, Object> result : results.values()) 294 { 295 result.put(SCC_UNIQUE_ID, result.get(getIdColumn())); 296 } 297 298 return results; 299 } 300 301 @Override 302 protected Map<String, Map<String, List<Object>>> getRemoteValues(Map<String, Object> parameters, Logger logger) 303 { 304 Map<String, Map<String, List<Object>>> remoteValues = new HashMap<>(); 305 306 Map<String, Map<String, Object>> results = internalSearch(parameters, 0, Integer.MAX_VALUE, null, logger); 307 308 if (results != null) 309 { 310 remoteValues = _sccHelper.organizeRemoteValuesByMetadata(results, _mapping); 311 } 312 313 return remoteValues; 314 } 315 316 @Override 317 protected List<ModifiableDefaultContent> _importOrSynchronizeContent(String idValue, Map<String, List<Object>> remoteValues, boolean forceImport, Logger logger) 318 { 319 return _importOrSynchronizeContent(idValue, _odfLang, remoteValues, forceImport, logger); 320 } 321 322 @Override 323 public List<ModifiableDefaultContent> importContent(String idValue, Map<String, Object> importParams, Logger logger) throws Exception 324 { 325 boolean isRequestAttributeOwner = false; 326 327 Request request = ContextHelper.getRequest(_context); 328 if (request.getAttribute(HANDLE_CONTENTS) == null) 329 { 330 request.setAttribute(HANDLE_CONTENTS, new HashSet<String>()); 331 isRequestAttributeOwner = true; 332 } 333 334 List<ModifiableDefaultContent> createdContents = new ArrayList<>(); 335 336 Map<String, Object> parameters = putIdParameter(idValue); 337 Map<String, Map<String, List<Object>>> results = getTransformedRemoteValues(parameters, logger); 338 if (!results.isEmpty()) 339 { 340 try 341 { 342 createdContents.add(_importContent(idValue, importParams, _odfLang, results.get(idValue), logger)); 343 } 344 catch (Exception e) 345 { 346 _nbError++; 347 logger.error("An error occurred while importing or synchronizing content", e); 348 } 349 } 350 351 if (isRequestAttributeOwner) 352 { 353 request.removeAttribute(HANDLE_CONTENTS); 354 } 355 356 return createdContents; 357 } 358 359 @Override 360 public void synchronizeContent(ModifiableDefaultContent content, Logger logger) throws Exception 361 { 362 boolean isRequestAttributeOwner = false; 363 364 Request request = ContextHelper.getRequest(_context); 365 if (request.getAttribute(HANDLE_CONTENTS) == null) 366 { 367 request.setAttribute(HANDLE_CONTENTS, new HashSet<String>()); 368 isRequestAttributeOwner = true; 369 } 370 371 super.synchronizeContent(content, logger); 372 373 if (isRequestAttributeOwner) 374 { 375 request.removeAttribute(HANDLE_CONTENTS); 376 } 377 } 378 379 @Override 380 protected Map<String, Object> putIdParameter(String idValue) 381 { 382 Map<String, Object> parameters = new HashMap<>(); 383 parameters.put(getIdField(), idValue); 384 return parameters; 385 } 386 387 /** 388 * Search the contents with the search parameters. Use id parameter to search an unique content. 389 * @param searchParams Search parameters 390 * @param logger The logger 391 * @return A Map of mapped metadatas extract from Apogée database ordered by content unique Apogée ID 392 */ 393 protected abstract List<Map<String, Object>> _search(Map<String, Object> searchParams, Logger logger); 394 395 /** 396 * Convert the {@link BigDecimal} values retrieved from database into long values 397 * @param searchResults The initial search results from database 398 * @return The converted search results 399 */ 400 protected List<Map<String, Object>> _convertBigDecimal(List<Map<String, Object>> searchResults) 401 { 402 List<Map<String, Object>> convertedSearchResults = new ArrayList<>(); 403 404 for (Map<String, Object> searchResult : searchResults) 405 { 406 for (String key : searchResult.keySet()) 407 { 408 searchResult.put(key, _convertBigDecimal(getContentType(), key, searchResult.get(key))); 409 } 410 411 convertedSearchResults.add(searchResult); 412 } 413 414 return convertedSearchResults; 415 } 416 417 /** 418 * Convert the object in parameter to a long if it's a {@link BigDecimal}, otherwise return the object itself. 419 * @param contentTypeId The content type of the parent content 420 * @param metadataName The metadata name 421 * @param objectToConvert The object to convert if necessary 422 * @return The converted object 423 */ 424 protected Object _convertBigDecimal(String contentTypeId, String metadataName, Object objectToConvert) 425 { 426 if (objectToConvert instanceof BigDecimal) 427 { 428 ContentType contentType = _contentTypeEP.getExtension(contentTypeId); 429 MetadataDefinition definition = contentType.getMetadataDefinition(metadataName); 430 if (definition != null) 431 { 432 MetadataType type = definition.getType(); 433 switch (type) 434 { 435 case DOUBLE: 436 return ((BigDecimal) objectToConvert).doubleValue(); 437 case LONG: 438 return ((BigDecimal) objectToConvert).longValue(); 439 default: 440 // Do nothing 441 break; 442 } 443 } 444 return ((BigDecimal) objectToConvert).toString(); 445 } 446 return objectToConvert; 447 } 448 449 /** 450 * Transform CLOB value to String value. 451 * @param value The input value 452 * @param idValue The identifier of the program 453 * @param logger The logger 454 * @return the same value, with CLOB transformed to String. 455 */ 456 protected Object _transformClobToString(Object value, String idValue, Logger logger) 457 { 458 if (value instanceof Clob) 459 { 460 Clob clob = (Clob) value; 461 try 462 { 463 String strValue = IOUtils.toString(clob.getCharacterStream()); 464 return CharMatcher.javaIsoControl().and(CharMatcher.anyOf("\r\n\t").negate()).removeFrom(strValue); 465 } 466 catch (SQLException | IOException e) 467 { 468 logger.error("Unable to get education add elements from the program '{}'.", idValue, e); 469 return null; 470 } 471 finally 472 { 473 try 474 { 475 clob.free(); 476 } 477 catch (SQLException e) 478 { 479 // Ignore the exception. 480 } 481 } 482 } 483 484 return value; 485 } 486 487 /** 488 * Get the name of the mapping. 489 * @return the mapping name 490 */ 491 protected abstract String getMappingName(); 492 493 /** 494 * Get the identifier column (can be a concatened column). 495 * @return the column id 496 */ 497 protected String getIdColumn() 498 { 499 return _idColumn; 500 } 501 502 @Override 503 public String getIdField() 504 { 505 return "apogeeSyncCode"; 506 } 507 508 @Override 509 public Set<String> getLocalAndExternalFields(Map<String, Object> additionalParameters) 510 { 511 return _syncFields; 512 } 513 514 @Override 515 public Set<String> getExternalOnlyFields(Map<String, Object> additionalParameters) 516 { 517 return _extFields; 518 } 519 520 @Override 521 protected boolean _fillContent(Map<String, List<Object>> remoteValues, ModifiableDefaultContent content, boolean create, Logger logger) 522 { 523 Map<String, Object> params = ImmutableMap.of("contentType", getContentType()); 524 ModifiableCompositeMetadata holder = content.getMetadataHolder(); 525 526 for (String metadataName : getRichTextFields()) 527 { 528 if (remoteValues.containsKey(metadataName)) 529 { 530 List<String> lines = new LinkedList<>(); 531 for (Object remoteText : remoteValues.get(metadataName)) 532 { 533 lines.add(remoteText.toString()); 534 } 535 536 String docbook = ContentImporterHelper.textToDocbook(lines.toArray(new String[lines.size()])); 537 boolean synchronize = getLocalAndExternalFields(params).contains(metadataName); 538 539 try (ByteArrayInputStream is = new ByteArrayInputStream(docbook.getBytes("UTF-8"))) 540 { 541 ModifiableRichText richText = ExternalizableMetadataHelper.getRichText(holder, metadataName, synchronize ? ExternalizableMetadataStatus.EXTERNAL : ExternalizableMetadataStatus.LOCAL, true); 542 richText.setInputStream(is); 543 richText.setMimeType("text/xml"); 544 richText.setLastModified(new Date()); 545 } 546 catch (IOException e) 547 { 548 logger.error("An error occured while parsing the rich text '{}' of the content '{}'", metadataName, content.getTitle(), e); 549 } 550 551 remoteValues.remove(metadataName); 552 } 553 } 554 555 boolean hasChanges = super._fillContent(remoteValues, content, create, logger); 556 hasChanges = _handleAdditionalMetadata(holder, create) || hasChanges; 557 558 if (!holder.hasMetadata(getIdField())) 559 { 560 holder.setMetadata(getIdField(), remoteValues.get(getIdField()).get(0).toString()); 561 hasChanges |= create; 562 } 563 564 return hasChanges; 565 } 566 567 /** 568 * Method to add additional metadata on import or synchronize. 569 * @param holder The holder of the content to update 570 * @param create If we are on creation mode 571 * @return <code>true</code> if changes has been made 572 */ 573 protected boolean _handleAdditionalMetadata(ModifiableCompositeMetadata holder, boolean create) 574 { 575 // Do nothing 576 return false; 577 } 578 579 /** 580 * Get the list of rich text fields of the imported content. 581 * @return The list of the rich text fields metadata name 582 */ 583 protected Set<String> getRichTextFields() 584 { 585 return new HashSet<>(); 586 } 587 588 @Override 589 protected void configureSearchModel() 590 { 591 for (ApogeeCriterion criterion : _criteria) 592 { 593 _searchModelConfiguration.addCriterion(criterion.getId(), criterion.getLabel(), criterion.getType()); 594 } 595 for (String columnName : _columns) 596 { 597 _searchModelConfiguration.addColumn(columnName); 598 } 599 } 600 601 @Override 602 protected boolean additionalCommonOperations(ModifiableDefaultContent content, Map<String, List<Object>> remoteValues, Map<String, Object> importParams, Logger logger) 603 { 604 boolean hasChanges = super.additionalCommonOperations(content, remoteValues, importParams, logger); 605 606 Object parentId = importParams != null && importParams.containsKey("parentId") ? importParams.get("parentId") : null; 607 ModifiableDefaultContent parentContent = parentId != null ? _resolver.resolveById(parentId.toString()) : null; 608 609 hasChanges = handleParent(content, parentContent, logger) || hasChanges; 610 hasChanges = handleChildren(content, logger) || hasChanges; 611 hasChanges = setAdditionalMetadata(content, remoteValues, logger) || hasChanges; 612 613 return hasChanges; 614 } 615 616 /** 617 * Set the parent metadata and invert relation. 618 * @param currentContent Current content 619 * @param parentContent Parent content to set 620 * @param logger The logger 621 * @return <code>true</code> if there are changes 622 */ 623 protected boolean handleParent(ModifiableDefaultContent currentContent, ModifiableDefaultContent parentContent, Logger logger) 624 { 625 // Nothing to do by default 626 return false; 627 } 628 629 /** 630 * Set the children metadata and invert relation, import and synchronize the children too. 631 * @param content Current content 632 * @param logger The logger 633 * @return <code>true</code> if there are changes 634 */ 635 protected boolean handleChildren(ModifiableDefaultContent content, Logger logger) 636 { 637 // Nothing to do by default 638 return false; 639 } 640 641 /** 642 * Set the additional metadata. 643 * @param content Current content 644 * @param remoteValues Values of the content 645 * @param logger The logger 646 * @return <code>true</code> if there are changes 647 */ 648 protected boolean setAdditionalMetadata(ModifiableDefaultContent content, Map<String, List<Object>> remoteValues, Logger logger) 649 { 650 // Nothing to do by default 651 return false; 652 } 653 654 @SuppressWarnings("unchecked") 655 private String _getSort(List<Object> sortList) 656 { 657 if (sortList != null) 658 { 659 StringBuilder sort = new StringBuilder(); 660 661 for (Object sortValueObj : sortList) 662 { 663 Map<String, Object> sortValue = (Map<String, Object>) sortValueObj; 664 665 sort.append(sortValue.get("property")); 666 if (sortValue.containsKey("direction")) 667 { 668 sort.append(" "); 669 sort.append(sortValue.get("direction")); 670 sort.append(","); 671 } 672 else 673 { 674 sort.append(" ASC,"); 675 } 676 } 677 678 sort.deleteCharAt(sort.length() - 1); 679 680 return sort.toString(); 681 } 682 683 return null; 684 } 685 686 @Override 687 public int getTotalCount(Map<String, Object> parameters, Logger logger) 688 { 689 // Remove empty parameters 690 Map<String, Object> searchParams = new HashMap<>(); 691 for (String parameterName : parameters.keySet()) 692 { 693 Object parameterValue = parameters.get(parameterName); 694 if (parameterValue != null && !parameterValue.toString().isEmpty()) 695 { 696 searchParams.put(parameterName, parameterValue); 697 } 698 } 699 700 searchParams.put("__count", true); 701 702 List<Map<String, Object>> results = _search(searchParams, logger); 703 if (results != null && !results.isEmpty()) 704 { 705 return Integer.valueOf(results.get(0).get("COUNT(*)").toString()).intValue(); 706 } 707 708 return 0; 709 } 710 711 @Override 712 protected ModifiableDefaultContent _importContent(String idValue, Map<String, Object> importParams, String lang, Map<String, List<Object>> remoteValues, Logger logger) throws Exception 713 { 714 ModifiableDefaultContent content = super._importContent(idValue, importParams, lang, _transformOrgUnitMetadata(remoteValues, logger), logger); 715 if (content != null) 716 { 717 addToHandleContents(content.getId()); 718 } 719 return content; 720 } 721 722 @Override 723 protected ModifiableDefaultContent _synchronizeContent(ModifiableDefaultContent content, Map<String, List<Object>> remoteValues, Logger logger) throws Exception 724 { 725 if (!addToHandleContents(content.getId())) 726 { 727 return content; 728 } 729 return super._synchronizeContent(content, _transformOrgUnitMetadata(remoteValues, logger), logger); 730 } 731 732 /** 733 * Import and synchronize children of the given content, then edit the structure of the content and its children. 734 * @param content Parent content 735 * @param sccModelId SCC model ID 736 * @param metadataName Metadata name to set 737 * @param invertMetadataName Metadata name of the invert relation 738 * @param logger The logger 739 * @return <code>true</code> if there are changes 740 */ 741 protected boolean importOrSynchronizeChildren(ModifiableDefaultContent content, String sccModelId, String metadataName, String invertMetadataName, Logger logger) 742 { 743 boolean hasChanges = false; 744 745 ModifiableCompositeMetadata cm = content.getMetadataHolder(); 746 747 if (removalSync() && cm.hasMetadata(metadataName)) 748 { 749 String[] children = cm.getStringArray(metadataName); 750 hasChanges = ExternalizableMetadataHelper.removeMetadataIfExists(cm, metadataName); 751 752 for (String child : children) 753 { 754 ModifiableDefaultContent childContent = _resolver.resolveById(child); 755 if (child != null) 756 { 757 hasChanges = _updateRelation(childContent.getMetadataHolder(), invertMetadataName, content, true) || hasChanges; 758 } 759 } 760 } 761 762 // Get the SCC for children 763 SynchronizableContentsCollection scc = _sccHelper.getSCCFromModelId(sccModelId); 764 765 // Search for children 766 if (scc != null && scc instanceof ApogeeSynchronizableContentsCollection) 767 { 768 String syncCode = cm.getString(getIdField()); 769 List<ModifiableDefaultContent> children = ((ApogeeSynchronizableContentsCollection) scc).importOrSynchronizeContents(ImmutableMap.of("parentCode", syncCode), logger); 770 771 hasChanges = ExternalizableMetadataHelper.setMetadata(cm, metadataName, children.toArray(new ModifiableDefaultContent[children.size()])) || hasChanges; 772 for (ModifiableDefaultContent child : children) 773 { 774 hasChanges = _updateRelation(child.getMetadataHolder(), invertMetadataName, content) || hasChanges; 775 } 776 } 777 778 return hasChanges; 779 } 780 781 @Override 782 public List<ModifiableDefaultContent> importOrSynchronizeContents(Map<String, Object> searchParams, Logger logger) 783 { 784 return _importOrSynchronizeContents(searchParams, true, logger); 785 } 786 787 /** 788 * Add the content ID to the handle contents list. 789 * @param contentId Content ID 790 * @return <code>true</code> if the content ID have been added, <code>false</code> is returned if the content ID already exists in the handle contents list. 791 */ 792 protected boolean addToHandleContents(String contentId) 793 { 794 Request request = ContextHelper.getRequest(_context); 795 @SuppressWarnings("unchecked") 796 Set<String> handleContents = (Set<String>) request.getAttribute(HANDLE_CONTENTS); 797 boolean added = handleContents.add(contentId); 798 request.setAttribute(HANDLE_CONTENTS, handleContents); 799 return added; 800 } 801 802 @SuppressWarnings("unchecked") 803 private Map<String, List<Object>> _transformOrgUnitMetadata(Map<String, List<Object>> remoteValues, Logger logger) 804 { 805 // Transform orgUnit values and import content if necessary (useful for Course and SubProgram) 806 SynchronizableContentsCollection scc = _sccHelper.getSCCFromModelId(OrgUnitSynchronizableContentsCollection.MODEL_ID); 807 808 List<Object> orgUnitCodes = remoteValues.get("orgUnit"); 809 if (orgUnitCodes != null && !orgUnitCodes.isEmpty()) 810 { 811 List<?> orgUnitContents = null; 812 String orgUnitCode = orgUnitCodes.get(0).toString(); 813 814 if (scc != null) 815 { 816 try 817 { 818 ModifiableDefaultContent orgUnitContent = scc.getContent(_odfLang, orgUnitCode); 819 if (orgUnitContent == null) 820 { 821 orgUnitContents = scc.importContent(orgUnitCode, null, logger); 822 } 823 else 824 { 825 orgUnitContents = ImmutableList.of(orgUnitContent); 826 } 827 } 828 catch (Exception e) 829 { 830 logger.error("An error occured during the import of the OrgUnit identified by the synchronization code '{}'", orgUnitCode, e); 831 } 832 } 833 834 if (orgUnitContents == null) 835 { 836 // Impossible link to orgUnit 837 remoteValues.remove("orgUnit"); 838 logger.warn("Impossible to import the OrgUnit with the synchronization code '{}', check if you set the OrgUnit SCC with the following model ID: '{}'", orgUnitCode, OrgUnitSynchronizableContentsCollection.MODEL_ID); 839 } 840 else 841 { 842 remoteValues.put("orgUnit", (List<Object>) orgUnitContents); 843 } 844 } 845 846 return remoteValues; 847 } 848 849 @Override 850 public boolean handleRightAssignmentContext() 851 { 852 // Rights on ODF contents are handled by ODFRightAssignmentContext 853 return false; 854 } 855}