001/* 002 * Copyright 2014 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.catalog; 017 018import java.io.IOException; 019import java.time.Duration; 020import java.time.temporal.ChronoUnit; 021import java.util.ArrayList; 022import java.util.Collection; 023import java.util.Date; 024import java.util.HashMap; 025import java.util.List; 026import java.util.Map; 027import java.util.Set; 028import java.util.concurrent.ExecutionException; 029import java.util.concurrent.Future; 030import java.util.stream.Collectors; 031 032import org.apache.avalon.framework.component.Component; 033import org.apache.avalon.framework.context.Context; 034import org.apache.avalon.framework.context.ContextException; 035import org.apache.avalon.framework.context.Contextualizable; 036import org.apache.avalon.framework.service.ServiceException; 037import org.apache.avalon.framework.service.ServiceManager; 038import org.apache.avalon.framework.service.Serviceable; 039import org.apache.cocoon.ProcessingException; 040import org.apache.cocoon.components.ContextHelper; 041import org.apache.cocoon.environment.Request; 042import org.apache.commons.lang.StringUtils; 043import org.apache.solr.client.solrj.SolrServerException; 044 045import org.ametys.cms.ObservationConstants; 046import org.ametys.cms.content.archive.ArchiveConstants; 047import org.ametys.cms.content.indexing.solr.SolrIndexer; 048import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 049import org.ametys.cms.data.ContentValue; 050import org.ametys.cms.repository.Content; 051import org.ametys.cms.repository.ContentQueryHelper; 052import org.ametys.cms.repository.ContentTypeExpression; 053import org.ametys.cms.repository.LanguageExpression; 054import org.ametys.cms.repository.ModifiableDefaultContent; 055import org.ametys.cms.repository.WorkflowAwareContent; 056import org.ametys.cms.workflow.ContentWorkflowHelper; 057import org.ametys.core.observation.Event; 058import org.ametys.core.observation.ObservationManager; 059import org.ametys.core.ui.Callable; 060import org.ametys.core.user.CurrentUserProvider; 061import org.ametys.odf.ODFHelper; 062import org.ametys.odf.ProgramItem; 063import org.ametys.odf.course.Course; 064import org.ametys.odf.course.CourseContainer; 065import org.ametys.odf.courselist.CourseList; 066import org.ametys.odf.courselist.CourseListContainer; 067import org.ametys.odf.coursepart.CoursePart; 068import org.ametys.odf.program.Program; 069import org.ametys.odf.program.ProgramFactory; 070import org.ametys.odf.program.TraversableProgramPart; 071import org.ametys.plugins.repository.AmetysObject; 072import org.ametys.plugins.repository.AmetysObjectIterable; 073import org.ametys.plugins.repository.AmetysObjectResolver; 074import org.ametys.plugins.repository.AmetysRepositoryException; 075import org.ametys.plugins.repository.ModifiableTraversableAmetysObject; 076import org.ametys.plugins.repository.RepositoryConstants; 077import org.ametys.plugins.repository.TraversableAmetysObject; 078import org.ametys.plugins.repository.UnknownAmetysObjectException; 079import org.ametys.plugins.repository.provider.RequestAttributeWorkspaceSelector; 080import org.ametys.plugins.repository.query.QueryHelper; 081import org.ametys.plugins.repository.query.SortCriteria; 082import org.ametys.plugins.repository.query.expression.AndExpression; 083import org.ametys.plugins.repository.query.expression.Expression; 084import org.ametys.plugins.repository.query.expression.Expression.Operator; 085import org.ametys.plugins.repository.query.expression.StringExpression; 086import org.ametys.runtime.plugin.component.AbstractLogEnabled; 087import org.ametys.runtime.plugin.component.PluginAware; 088 089import com.opensymphony.workflow.WorkflowException; 090 091/** 092 * Component to handle ODF catalogs 093 */ 094public class CatalogsManager extends AbstractLogEnabled implements Serviceable, Component, PluginAware, Contextualizable 095{ 096 /** Avalon Role */ 097 public static final String ROLE = CatalogsManager.class.getName(); 098 099 private AmetysObjectResolver _resolver; 100 101 private CopyCatalogUpdaterExtensionPoint _copyUpdaterEP; 102 103 private ObservationManager _observationManager; 104 105 private CurrentUserProvider _userProvider; 106 107 private ContentWorkflowHelper _contentWorkflowHelper; 108 109 private String _pluginName; 110 111 private ODFHelper _odfHelper; 112 113 private ContentTypeExtensionPoint _cTypeEP; 114 115 private Context _context; 116 117 private SolrIndexer _solrIndexer; 118 119 private String _defaultCatalogId; 120 121 public void contextualize(Context context) throws ContextException 122 { 123 _context = context; 124 } 125 126 @Override 127 public void service(ServiceManager manager) throws ServiceException 128 { 129 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 130 _copyUpdaterEP = (CopyCatalogUpdaterExtensionPoint) manager.lookup(CopyCatalogUpdaterExtensionPoint.ROLE); 131 _observationManager = (ObservationManager) manager.lookup(ObservationManager.ROLE); 132 _userProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE); 133 _contentWorkflowHelper = (ContentWorkflowHelper) manager.lookup(ContentWorkflowHelper.ROLE); 134 _odfHelper = (ODFHelper) manager.lookup(ODFHelper.ROLE); 135 _cTypeEP = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE); 136 _solrIndexer = (SolrIndexer) manager.lookup(SolrIndexer.ROLE); 137 } 138 139 public void setPluginInfo(String pluginName, String featureName, String id) 140 { 141 _pluginName = pluginName; 142 } 143 144 /** 145 * Get the list of catalogs 146 * @return the catalogs 147 */ 148 public List<Catalog> getCatalogs() 149 { 150 List<Catalog> result = new ArrayList<>(); 151 152 TraversableAmetysObject catalogsNode = getCatalogsRootNode(); 153 154 AmetysObjectIterable<Catalog> catalogs = catalogsNode.getChildren(); 155 for (Catalog catalog : catalogs) 156 { 157 result.add(catalog); 158 } 159 160 return result; 161 } 162 163 /** 164 * Get a catalog matching with the given name 165 * @param name The name 166 * @return a catalog, or null if not found 167 */ 168 public Catalog getCatalog(String name) 169 { 170 ModifiableTraversableAmetysObject catalogsNode = getCatalogsRootNode(); 171 172 if (StringUtils.isNotEmpty(name) && catalogsNode.hasChild(name)) 173 { 174 return catalogsNode.getChild(name); 175 } 176 177 // Not found 178 return null; 179 } 180 181 /** 182 * Returns the name of the default catalog 183 * @return the name of the default catalog 184 */ 185 @Callable 186 public String getDefaultCatalogName() 187 { 188 Catalog defaultCatalog = getDefaultCatalog(); 189 return defaultCatalog != null ? defaultCatalog.getName() : null; 190 } 191 192 /** 193 * Returns the default catalog 194 * @return the default catalog or null if no default catalog was defined. 195 */ 196 public synchronized Catalog getDefaultCatalog() 197 { 198 if (_defaultCatalogId == null) 199 { 200 updateDefaultCatalog(); 201 } 202 203 return _resolver.resolveById(_defaultCatalogId); 204 } 205 206 /** 207 * Updates the default catalog (if it's null or if the user has updated it). 208 */ 209 void updateDefaultCatalog() 210 { 211 List<Catalog> catalogs = getCatalogs(); 212 for (Catalog catalog : catalogs) 213 { 214 if (catalog.isDefault()) 215 { 216 _defaultCatalogId = catalog.getId(); 217 return; 218 } 219 } 220 221 // If no default catalog found, get the only catalog if it exists 222 if (catalogs.size() == 1) 223 { 224 _defaultCatalogId = catalogs.get(0).getId(); 225 } 226 } 227 228 /** 229 * Get the name of the catalog of a ODF content 230 * @param contentId The id of content 231 * @return The catalog's name 232 */ 233 @Callable 234 public String getContentCatalog(String contentId) 235 { 236 Content content = _resolver.resolveById(contentId); 237 238 if (content instanceof ProgramItem) 239 { 240 return ((ProgramItem) content).getCatalog(); 241 } 242 243 // Get catalog from its parents (unecessary ?) 244 AmetysObject parent = content.getParent(); 245 while (parent != null) 246 { 247 if (parent instanceof ProgramItem) 248 { 249 return ((ProgramItem) parent).getCatalog(); 250 } 251 parent = parent.getParent(); 252 } 253 254 return null; 255 } 256 257 /** 258 * Determines if the catalog can be modified from the given content 259 * @param contentId The content id 260 * @return A map with success=false if the catalog cannot be edited 261 */ 262 @Callable 263 public Map<String, Object> canEditCatalog(String contentId) 264 { 265 Map<String, Object> result = new HashMap<>(); 266 267 Content content = _resolver.resolveById(contentId); 268 269 if (content instanceof ProgramItem) 270 { 271 if (_isReferenced(content)) 272 { 273 result.put("success", false); 274 result.put("error", "referenced"); 275 } 276 else if (_hasSharedContent((ProgramItem) content, (ProgramItem) content)) 277 { 278 result.put("success", false); 279 result.put("error", "hasSharedContent"); 280 } 281 else 282 { 283 result.put("success", true); 284 } 285 286 } 287 else 288 { 289 result.put("success", false); 290 result.put("error", "typeError"); 291 } 292 293 return result; 294 } 295 296 private boolean _isReferenced (Content content) 297 { 298 return !_odfHelper.getParentProgramItems((ProgramItem) content).isEmpty(); 299 } 300 301 private boolean _hasSharedContent (ProgramItem rootProgramItem, ProgramItem programItem) 302 { 303 List<ProgramItem> children = _odfHelper.getChildProgramItems(programItem); 304 305 for (ProgramItem child : children) 306 { 307 if (_isShared(rootProgramItem, child)) 308 { 309 return true; 310 } 311 } 312 313 if (programItem instanceof Course) 314 { 315 List<CoursePart> courseParts = ((Course) programItem).getCourseParts(); 316 for (CoursePart coursePart : courseParts) 317 { 318 List<ProgramItem> parentCourses = coursePart.getCourses() 319 .stream() 320 .map(ProgramItem.class::cast) 321 .collect(Collectors.toList()); 322 if (parentCourses.size() > 1 && !_isPartOfSameStructure(rootProgramItem, parentCourses)) 323 { 324 return true; 325 } 326 } 327 } 328 329 return false; 330 } 331 332 private boolean _isShared(ProgramItem rootProgramItem, ProgramItem programItem) 333 { 334 try 335 { 336 List<ProgramItem> parents = _odfHelper.getParentProgramItems(programItem); 337 if ((parents.size() > 1 && !_isPartOfSameStructure(rootProgramItem, parents)) || _hasSharedContent(rootProgramItem, programItem)) 338 { 339 return true; 340 } 341 } 342 catch (UnknownAmetysObjectException e) 343 { 344 // Nothing 345 } 346 347 return false; 348 } 349 350 private boolean _isPartOfSameStructure(ProgramItem rootProgramItem, List<ProgramItem> programItems) 351 { 352 for (ProgramItem programItem : programItems) 353 { 354 List<List<ProgramItem>> ancestorPaths = _odfHelper.getPathOfAncestors(programItem); 355 356 boolean isPartOfInitalStructure = false; 357 for (List<ProgramItem> ancestorPath : ancestorPaths) 358 { 359 for (ProgramItem pathSegment : ancestorPath) 360 { 361 if (pathSegment.equals(rootProgramItem)) 362 { 363 isPartOfInitalStructure = true; 364 break; 365 } 366 } 367 } 368 369 if (!isPartOfInitalStructure) 370 { 371 // The content is shared outside the program item to edit 372 return false; 373 } 374 } 375 376 return true; 377 } 378 379 /** 380 * Set the catalog of a content. This will modify recursively the catalog of referenced children 381 * @param catalog The catalog 382 * @param contentId The id of content to edit 383 * @throws WorkflowException if an error occurred 384 */ 385 @Callable 386 public void setContentCatalog(String catalog, String contentId) throws WorkflowException 387 { 388 Content content = _resolver.resolveById(contentId); 389 390 if (content instanceof ProgramItem) 391 { 392 _setCatalog(content, catalog); 393 } 394 else 395 { 396 throw new IllegalArgumentException("You can not edit the catalog of the content " + contentId); 397 } 398 } 399 400 private void _setCatalog (Content content, String catalogName) throws WorkflowException 401 { 402 if (content instanceof ProgramItem) 403 { 404 String oldCatalog = ((ProgramItem) content).getCatalog(); 405 if (!catalogName.equals(oldCatalog)) 406 { 407 ((ProgramItem) content).setCatalog(catalogName); 408 409 if (content instanceof WorkflowAwareContent) 410 { 411 _applyChanges((WorkflowAwareContent) content); 412 } 413 } 414 } 415 else if (content instanceof CoursePart) 416 { 417 String oldCatalog = ((CoursePart) content).getCatalog(); 418 if (!catalogName.equals(oldCatalog)) 419 { 420 ((CoursePart) content).setCatalog(catalogName); 421 422 if (content instanceof WorkflowAwareContent) 423 { 424 _applyChanges((WorkflowAwareContent) content); 425 } 426 } 427 } 428 429 _setCatalogToChildren(content, catalogName); 430 } 431 432 private void _setCatalogToChildren (Content content, String catalogName) throws WorkflowException 433 { 434 if (content instanceof TraversableProgramPart) 435 { 436 ContentValue[] children = content.getValue(TraversableProgramPart.CHILD_PROGRAM_PARTS, false, new ContentValue[0]); 437 for (ContentValue child : children) 438 { 439 try 440 { 441 _setCatalog(child.getContent(), catalogName); 442 } 443 catch (UnknownAmetysObjectException e) 444 { 445 // Nothing 446 } 447 } 448 } 449 450 if (content instanceof CourseContainer) 451 { 452 for (Course course : ((CourseContainer) content).getCourses()) 453 { 454 _setCatalog(course, catalogName); 455 } 456 } 457 458 if (content instanceof CourseListContainer) 459 { 460 for (CourseList cl : ((CourseListContainer) content).getCourseLists()) 461 { 462 _setCatalog(cl, catalogName); 463 } 464 } 465 466 if (content instanceof Course) 467 { 468 for (CoursePart coursePart : ((Course) content).getCourseParts()) 469 { 470 _setCatalog(coursePart, catalogName); 471 } 472 } 473 } 474 475 private void _applyChanges(WorkflowAwareContent content) throws WorkflowException 476 { 477 ((ModifiableDefaultContent) content).setLastContributor(_userProvider.getUser()); 478 ((ModifiableDefaultContent) content).setLastModified(new Date()); 479 480 // Remove the proposal date. 481 content.setProposalDate(null); 482 483 // Save changes 484 content.saveChanges(); 485 486 // Notify listeners 487 Map<String, Object> eventParams = new HashMap<>(); 488 eventParams.put(ObservationConstants.ARGS_CONTENT, content); 489 eventParams.put(ObservationConstants.ARGS_CONTENT_ID, content.getId()); 490 _observationManager.notify(new Event(ObservationConstants.EVENT_CONTENT_MODIFIED, _userProvider.getUser(), eventParams)); 491 492 _contentWorkflowHelper.doAction(content, 22); 493 } 494 495 /** 496 * Get the root catalogs storage object. 497 * @return the root catalogs node 498 * @throws AmetysRepositoryException if a repository error occurs. 499 */ 500 public ModifiableTraversableAmetysObject getCatalogsRootNode() throws AmetysRepositoryException 501 { 502 String originalWorkspace = null; 503 Request request = ContextHelper.getRequest(_context); 504 try 505 { 506 originalWorkspace = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 507 if (ArchiveConstants.ARCHIVE_WORKSPACE.equals(originalWorkspace)) 508 { 509 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, RepositoryConstants.DEFAULT_WORKSPACE); 510 } 511 512 ModifiableTraversableAmetysObject rootNode = _resolver.resolveByPath("/"); 513 ModifiableTraversableAmetysObject pluginsNode = _getOrCreateNode(rootNode, "ametys:plugins", "ametys:unstructured"); 514 ModifiableTraversableAmetysObject pluginNode = _getOrCreateNode(pluginsNode, _pluginName, "ametys:unstructured"); 515 516 return _getOrCreateNode(pluginNode, "catalogs", "ametys:unstructured"); 517 } 518 catch (AmetysRepositoryException e) 519 { 520 throw new AmetysRepositoryException("Unable to get the ODF catalogs root node", e); 521 } 522 finally 523 { 524 if (ArchiveConstants.ARCHIVE_WORKSPACE.equals(originalWorkspace)) 525 { 526 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, originalWorkspace); 527 } 528 } 529 } 530 531 /** 532 * Create a new catalog 533 * @param name The unique name 534 * @param title The title of catalog 535 * @return the created catalog 536 */ 537 public Catalog createCatalog(String name, String title) 538 { 539 Catalog newCatalog = null; 540 541 ModifiableTraversableAmetysObject catalogsNode = getCatalogsRootNode(); 542 543 newCatalog = catalogsNode.createChild(name, "ametys:catalog"); 544 newCatalog.setTitle(title); 545 546 if (getCatalogs().size() == 1) 547 { 548 newCatalog.setDefault(true); 549 } 550 return newCatalog; 551 } 552 553 /** 554 * Get the programs of a catalog for all languages 555 * @param catalog The code of catalog 556 * @return The programs 557 */ 558 public AmetysObjectIterable<Program> getPrograms (String catalog) 559 { 560 return getPrograms(catalog, null); 561 } 562 563 /** 564 * Get the program's items of a catalog for all languages 565 * @param catalog The code of catalog 566 * @return The {@link ProgramItem} 567 */ 568 public AmetysObjectIterable<ProgramItem> getProgramItems(String catalog) 569 { 570 List<Expression> exprs = new ArrayList<>(); 571 572 exprs.add(_cTypeEP.createHierarchicalCTExpression(ProgramItem.PROGRAM_ITEM_CONTENT_TYPE)); 573 exprs.add(new StringExpression(ProgramItem.CATALOG, Operator.EQ, catalog)); 574 575 Expression programItemsExpression = new AndExpression(exprs.toArray(new Expression[exprs.size()])); 576 577 // Add sort criteria to get size 578 SortCriteria sortCriteria = new SortCriteria(); 579 sortCriteria.addCriterion(Content.ATTRIBUTE_TITLE, true, true); 580 581 String query = ContentQueryHelper.getContentXPathQuery(programItemsExpression, sortCriteria); 582 return _resolver.query(query); 583 } 584 585 /** 586 * Get the programs of a catalog 587 * @param catalog The code of catalog 588 * @param lang The language. Can be null to get programs for all languages 589 * @return The programs 590 */ 591 public AmetysObjectIterable<Program> getPrograms (String catalog, String lang) 592 { 593 List<Expression> exprs = new ArrayList<>(); 594 exprs.add(new ContentTypeExpression(Operator.EQ, ProgramFactory.PROGRAM_CONTENT_TYPE)); 595 exprs.add(new StringExpression(ProgramItem.CATALOG, Operator.EQ, catalog)); 596 if (lang != null) 597 { 598 exprs.add(new LanguageExpression(Operator.EQ, lang)); 599 } 600 601 Expression programsExpression = new AndExpression(exprs.toArray(new Expression[exprs.size()])); 602 603 // Add sort criteria to get size 604 SortCriteria sortCriteria = new SortCriteria(); 605 sortCriteria.addCriterion(Content.ATTRIBUTE_TITLE, true, true); 606 607 String programsQuery = QueryHelper.getXPathQuery(null, ProgramFactory.PROGRAM_NODETYPE, programsExpression, sortCriteria); 608 return _resolver.query(programsQuery); 609 } 610 611 /** 612 * Copy the programs and its hierarchy from a catalog to another. 613 * The referenced courses are NOT copied. 614 * @param catalog The new catalog to populate 615 * @param catalogToCopy The catalog from which we copy the programs. 616 * @throws ProcessingException If an error occurred during copy 617 */ 618 public void copyCatalog(Catalog catalog, Catalog catalogToCopy) throws ProcessingException 619 { 620 String catalogToCopyName = catalogToCopy.getName(); 621 String catalogName = catalog.getName(); 622 String [] handledEventIds = new String[] {ObservationConstants.EVENT_CONTENT_ADDED, ObservationConstants.EVENT_CONTENT_MODIFIED, ObservationConstants.EVENT_CONTENT_WORKFLOW_CHANGED}; 623 try 624 { 625 Map<String, String> copiedPrograms = new HashMap<>(); 626 Map<String, String> copiedSubPrograms = new HashMap<>(); 627 Map<String, String> copiedContainers = new HashMap<>(); 628 Map<String, String> copiedCourseLists = new HashMap<>(); 629 Map<String, String> copiedCourses = new HashMap<>(); 630 Map<String, String> copiedCourseParts = new HashMap<>(); 631 632 Set<String> copyUpdaters = _copyUpdaterEP.getExtensionsIds(); 633 634 AmetysObjectIterable<Program> programs = getPrograms(catalogToCopyName); 635 636 // Do NOT commit yet to Solr in order to improve perfs 637 _observationManager.addArgumentForEvents(handledEventIds, ObservationConstants.ARGS_CONTENT_COMMIT, false); 638 639 long start = System.currentTimeMillis(); 640 641 getLogger().debug("Begin to iterate over programs for copying them"); 642 643 for (Program program : programs) 644 { 645 if (getLogger().isDebugEnabled()) 646 { 647 getLogger().debug("Start copying program '{}' (name: '{}', title: '{}')...", program.getId(), program.getName(), program.getTitle()); 648 } 649 650 Program newProgram = _odfHelper.copyProgramItem(program, catalogName, true, copiedPrograms, copiedSubPrograms, copiedContainers, copiedCourseLists, copiedCourses, copiedCourseParts); 651 652 for (String updaterId : copyUpdaters) 653 { 654 // Call updaters after copy of program 655 CopyCatalogUpdater updater = _copyUpdaterEP.getExtension(updaterId); 656 updater.updateContent(catalogToCopyName, catalogName, program, newProgram); 657 } 658 } 659 660 for (String updaterId : copyUpdaters) 661 { 662 // Call updaters after full copy of catalog 663 CopyCatalogUpdater updater = _copyUpdaterEP.getExtension(updaterId); 664 updater.updateContents(catalogToCopyName, catalogName, copiedPrograms, copiedSubPrograms, copiedContainers, copiedCourseLists, copiedCourses, copiedCourseParts); 665 } 666 667 // Workflow 668 _addCopyStep(copiedPrograms.values()); 669 _addCopyStep(copiedSubPrograms.values()); 670 _addCopyStep(copiedContainers.values()); 671 _addCopyStep(copiedCourseLists.values()); 672 _addCopyStep(copiedCourses.values()); 673 _addCopyStep(copiedCourseParts.values()); 674 675 if (getLogger().isDebugEnabled()) 676 { 677 getLogger().debug("End of iteration over programs for copying them ({})", Duration.of((System.currentTimeMillis() - start) / 1000, ChronoUnit.SECONDS)); 678 } 679 680 } 681 catch (AmetysRepositoryException | WorkflowException e) 682 { 683 getLogger().error("Copy of items of catalog {} into catalog {} has failed", catalogToCopyName, catalogName); 684 throw new ProcessingException("Failed to copy catalog", e); 685 } 686 finally 687 { 688 _observationManager.removeArgumentForEvents(handledEventIds, ObservationConstants.ARGS_CONTENT_COMMIT); 689 690 // Before trying to commit, be sure all the async observers of the current request are finished 691 for (Future future : _observationManager.getFuturesForRequest()) 692 { 693 try 694 { 695 future.get(); 696 } 697 catch (ExecutionException | InterruptedException e) 698 { 699 getLogger().info("An exception occured when calling #get() on Future result of an observer." , e); 700 } 701 } 702 703 // Commit all uncommited changes 704 try 705 { 706 _solrIndexer.commit(); 707 708 getLogger().debug("Copied contents are now committed into Solr."); 709 } 710 catch (IOException | SolrServerException e) 711 { 712 getLogger().error("Impossible to commit changes", e); 713 } 714 } 715 } 716 717 private void _addCopyStep(Collection<String> contentIds) throws AmetysRepositoryException, WorkflowException 718 { 719 for (String contentId : contentIds) 720 { 721 WorkflowAwareContent content = _resolver.resolveById(contentId); 722 _contentWorkflowHelper.doAction(content, getCopyActionId()); 723 } 724 } 725 726 /** 727 * Get the workflow action id for copy. 728 * @return The workflow action id 729 */ 730 protected int getCopyActionId() 731 { 732 return 210; 733 } 734 735 /** 736 * Delete catalog 737 * @param id the id of catalog 738 */ 739 public void deleteCatalog (String id) 740 { 741 try 742 { 743 Catalog catalog = _resolver.resolveById(id); 744 if (catalog != null) 745 { 746 catalog.remove(); 747 catalog.saveChanges(); 748 } 749 } 750 catch (UnknownAmetysObjectException e) 751 { 752 // Nothing 753 } 754 755 } 756 757 private ModifiableTraversableAmetysObject _getOrCreateNode(ModifiableTraversableAmetysObject parentNode, String nodeName, String nodeType) throws AmetysRepositoryException 758 { 759 ModifiableTraversableAmetysObject definitionsNode; 760 if (parentNode.hasChild(nodeName)) 761 { 762 definitionsNode = parentNode.getChild(nodeName); 763 } 764 else 765 { 766 definitionsNode = parentNode.createChild(nodeName, nodeType); 767 parentNode.saveChanges(); 768 } 769 return definitionsNode; 770 } 771}