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 break; 218 } 219 } 220 } 221 222 /** 223 * Get the name of the catalog of a ODF content 224 * @param contentId The id of content 225 * @return The catalog's name 226 */ 227 @Callable 228 public String getContentCatalog(String contentId) 229 { 230 Content content = _resolver.resolveById(contentId); 231 232 if (content instanceof ProgramItem) 233 { 234 return ((ProgramItem) content).getCatalog(); 235 } 236 237 // Get catalog from its parents (unecessary ?) 238 AmetysObject parent = content.getParent(); 239 while (parent != null) 240 { 241 if (parent instanceof ProgramItem) 242 { 243 return ((ProgramItem) parent).getCatalog(); 244 } 245 parent = parent.getParent(); 246 } 247 248 return null; 249 } 250 251 /** 252 * Determines if the catalog can be modified from the given content 253 * @param contentId The content id 254 * @return A map with success=false if the catalog cannot be edited 255 */ 256 @Callable 257 public Map<String, Object> canEditCatalog(String contentId) 258 { 259 Map<String, Object> result = new HashMap<>(); 260 261 Content content = _resolver.resolveById(contentId); 262 263 if (content instanceof ProgramItem) 264 { 265 if (_isReferenced(content)) 266 { 267 result.put("success", false); 268 result.put("error", "referenced"); 269 } 270 else if (_hasSharedContent((ProgramItem) content, (ProgramItem) content)) 271 { 272 result.put("success", false); 273 result.put("error", "hasSharedContent"); 274 } 275 else 276 { 277 result.put("success", true); 278 } 279 280 } 281 else 282 { 283 result.put("success", false); 284 result.put("error", "typeError"); 285 } 286 287 return result; 288 } 289 290 private boolean _isReferenced (Content content) 291 { 292 return !_odfHelper.getParentProgramItems((ProgramItem) content).isEmpty(); 293 } 294 295 private boolean _hasSharedContent (ProgramItem rootProgramItem, ProgramItem programItem) 296 { 297 List<ProgramItem> children = _odfHelper.getChildProgramItems(programItem); 298 299 for (ProgramItem child : children) 300 { 301 if (_isShared(rootProgramItem, child)) 302 { 303 return true; 304 } 305 } 306 307 if (programItem instanceof Course) 308 { 309 List<CoursePart> courseParts = ((Course) programItem).getCourseParts(); 310 for (CoursePart coursePart : courseParts) 311 { 312 List<ProgramItem> parentCourses = coursePart.getCourses() 313 .stream() 314 .map(ProgramItem.class::cast) 315 .collect(Collectors.toList()); 316 if (parentCourses.size() > 1 && !_isPartOfSameStructure(rootProgramItem, parentCourses)) 317 { 318 return true; 319 } 320 } 321 } 322 323 return false; 324 } 325 326 private boolean _isShared(ProgramItem rootProgramItem, ProgramItem programItem) 327 { 328 try 329 { 330 List<ProgramItem> parents = _odfHelper.getParentProgramItems(programItem); 331 if ((parents.size() > 1 && !_isPartOfSameStructure(rootProgramItem, parents)) || _hasSharedContent(rootProgramItem, programItem)) 332 { 333 return true; 334 } 335 } 336 catch (UnknownAmetysObjectException e) 337 { 338 // Nothing 339 } 340 341 return false; 342 } 343 344 private boolean _isPartOfSameStructure(ProgramItem rootProgramItem, List<ProgramItem> programItems) 345 { 346 for (ProgramItem programItem : programItems) 347 { 348 List<List<ProgramItem>> ancestorPaths = _odfHelper.getPathOfAncestors(programItem); 349 350 boolean isPartOfInitalStructure = false; 351 for (List<ProgramItem> ancestorPath : ancestorPaths) 352 { 353 for (ProgramItem pathSegment : ancestorPath) 354 { 355 if (pathSegment.equals(rootProgramItem)) 356 { 357 isPartOfInitalStructure = true; 358 break; 359 } 360 } 361 } 362 363 if (!isPartOfInitalStructure) 364 { 365 // The content is shared outside the program item to edit 366 return false; 367 } 368 } 369 370 return true; 371 } 372 373 /** 374 * Set the catalog of a content. This will modify recursively the catalog of referenced children 375 * @param catalog The catalog 376 * @param contentId The id of content to edit 377 * @throws WorkflowException if an error occurred 378 */ 379 @Callable 380 public void setContentCatalog(String catalog, String contentId) throws WorkflowException 381 { 382 Content content = _resolver.resolveById(contentId); 383 384 if (content instanceof ProgramItem) 385 { 386 _setCatalog(content, catalog); 387 } 388 else 389 { 390 throw new IllegalArgumentException("You can not edit the catalog of the content " + contentId); 391 } 392 } 393 394 private void _setCatalog (Content content, String catalogName) throws WorkflowException 395 { 396 if (content instanceof ProgramItem) 397 { 398 String oldCatalog = ((ProgramItem) content).getCatalog(); 399 if (!catalogName.equals(oldCatalog)) 400 { 401 ((ProgramItem) content).setCatalog(catalogName); 402 403 if (content instanceof WorkflowAwareContent) 404 { 405 _applyChanges((WorkflowAwareContent) content); 406 } 407 } 408 } 409 else if (content instanceof CoursePart) 410 { 411 String oldCatalog = ((CoursePart) content).getCatalog(); 412 if (!catalogName.equals(oldCatalog)) 413 { 414 ((CoursePart) content).setCatalog(catalogName); 415 416 if (content instanceof WorkflowAwareContent) 417 { 418 _applyChanges((WorkflowAwareContent) content); 419 } 420 } 421 } 422 423 _setCatalogToChildren(content, catalogName); 424 } 425 426 private void _setCatalogToChildren (Content content, String catalogName) throws WorkflowException 427 { 428 if (content instanceof TraversableProgramPart) 429 { 430 ContentValue[] children = content.getValue(TraversableProgramPart.CHILD_PROGRAM_PARTS, false, new ContentValue[0]); 431 for (ContentValue child : children) 432 { 433 try 434 { 435 _setCatalog(child.getContent(), catalogName); 436 } 437 catch (UnknownAmetysObjectException e) 438 { 439 // Nothing 440 } 441 } 442 } 443 444 if (content instanceof CourseContainer) 445 { 446 for (Course course : ((CourseContainer) content).getCourses()) 447 { 448 _setCatalog(course, catalogName); 449 } 450 } 451 452 if (content instanceof CourseListContainer) 453 { 454 for (CourseList cl : ((CourseListContainer) content).getCourseLists()) 455 { 456 _setCatalog(cl, catalogName); 457 } 458 } 459 460 if (content instanceof Course) 461 { 462 for (CoursePart coursePart : ((Course) content).getCourseParts()) 463 { 464 _setCatalog(coursePart, catalogName); 465 } 466 } 467 } 468 469 private void _applyChanges(WorkflowAwareContent content) throws WorkflowException 470 { 471 ((ModifiableDefaultContent) content).setLastContributor(_userProvider.getUser()); 472 ((ModifiableDefaultContent) content).setLastModified(new Date()); 473 474 // Remove the proposal date. 475 content.setProposalDate(null); 476 477 // Save changes 478 content.saveChanges(); 479 480 // Notify listeners 481 Map<String, Object> eventParams = new HashMap<>(); 482 eventParams.put(ObservationConstants.ARGS_CONTENT, content); 483 eventParams.put(ObservationConstants.ARGS_CONTENT_ID, content.getId()); 484 _observationManager.notify(new Event(ObservationConstants.EVENT_CONTENT_MODIFIED, _userProvider.getUser(), eventParams)); 485 486 _contentWorkflowHelper.doAction(content, 22); 487 } 488 489 /** 490 * Get the root catalogs storage object. 491 * @return the root catalogs node 492 * @throws AmetysRepositoryException if a repository error occurs. 493 */ 494 public ModifiableTraversableAmetysObject getCatalogsRootNode() throws AmetysRepositoryException 495 { 496 String originalWorkspace = null; 497 Request request = ContextHelper.getRequest(_context); 498 try 499 { 500 originalWorkspace = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 501 if (ArchiveConstants.ARCHIVE_WORKSPACE.equals(originalWorkspace)) 502 { 503 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, RepositoryConstants.DEFAULT_WORKSPACE); 504 } 505 506 ModifiableTraversableAmetysObject rootNode = _resolver.resolveByPath("/"); 507 ModifiableTraversableAmetysObject pluginsNode = _getOrCreateNode(rootNode, "ametys:plugins", "ametys:unstructured"); 508 ModifiableTraversableAmetysObject pluginNode = _getOrCreateNode(pluginsNode, _pluginName, "ametys:unstructured"); 509 510 return _getOrCreateNode(pluginNode, "catalogs", "ametys:unstructured"); 511 } 512 catch (AmetysRepositoryException e) 513 { 514 throw new AmetysRepositoryException("Unable to get the ODF catalogs root node", e); 515 } 516 finally 517 { 518 if (ArchiveConstants.ARCHIVE_WORKSPACE.equals(originalWorkspace)) 519 { 520 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, originalWorkspace); 521 } 522 } 523 } 524 525 /** 526 * Create a new catalog 527 * @param name The unique name 528 * @param title The title of catalog 529 * @return the created catalog 530 */ 531 public Catalog createCatalog(String name, String title) 532 { 533 Catalog newCatalog = null; 534 535 ModifiableTraversableAmetysObject catalogsNode = getCatalogsRootNode(); 536 537 newCatalog = catalogsNode.createChild(name, "ametys:catalog"); 538 newCatalog.setTitle(title); 539 540 if (getCatalogs().size() == 1) 541 { 542 newCatalog.setDefault(true); 543 } 544 return newCatalog; 545 } 546 547 /** 548 * Get the programs of a catalog for all languages 549 * @param catalog The code of catalog 550 * @return The programs 551 */ 552 public AmetysObjectIterable<Program> getPrograms (String catalog) 553 { 554 return getPrograms(catalog, null); 555 } 556 557 /** 558 * Get the program's items of a catalog for all languages 559 * @param catalog The code of catalog 560 * @return The {@link ProgramItem} 561 */ 562 public AmetysObjectIterable<ProgramItem> getProgramItems(String catalog) 563 { 564 List<Expression> exprs = new ArrayList<>(); 565 566 exprs.add(_cTypeEP.createHierarchicalCTExpression(ProgramItem.PROGRAM_ITEM_CONTENT_TYPE)); 567 exprs.add(new StringExpression(ProgramItem.CATALOG, Operator.EQ, catalog)); 568 569 Expression programItemsExpression = new AndExpression(exprs.toArray(new Expression[exprs.size()])); 570 571 // Add sort criteria to get size 572 SortCriteria sortCriteria = new SortCriteria(); 573 sortCriteria.addCriterion(Content.ATTRIBUTE_TITLE, true, true); 574 575 String query = ContentQueryHelper.getContentXPathQuery(programItemsExpression, sortCriteria); 576 return _resolver.query(query); 577 } 578 579 /** 580 * Get the programs of a catalog 581 * @param catalog The code of catalog 582 * @param lang The language. Can be null to get programs for all languages 583 * @return The programs 584 */ 585 public AmetysObjectIterable<Program> getPrograms (String catalog, String lang) 586 { 587 List<Expression> exprs = new ArrayList<>(); 588 exprs.add(new ContentTypeExpression(Operator.EQ, ProgramFactory.PROGRAM_CONTENT_TYPE)); 589 exprs.add(new StringExpression(ProgramItem.CATALOG, Operator.EQ, catalog)); 590 if (lang != null) 591 { 592 exprs.add(new LanguageExpression(Operator.EQ, lang)); 593 } 594 595 Expression programsExpression = new AndExpression(exprs.toArray(new Expression[exprs.size()])); 596 597 // Add sort criteria to get size 598 SortCriteria sortCriteria = new SortCriteria(); 599 sortCriteria.addCriterion(Content.ATTRIBUTE_TITLE, true, true); 600 601 String programsQuery = QueryHelper.getXPathQuery(null, ProgramFactory.PROGRAM_NODETYPE, programsExpression, sortCriteria); 602 return _resolver.query(programsQuery); 603 } 604 605 /** 606 * Copy the programs and its hierarchy from a catalog to another. 607 * The referenced courses are NOT copied. 608 * @param catalog The new catalog to populate 609 * @param catalogToCopy The catalog from which we copy the programs. 610 * @throws ProcessingException If an error occurred during copy 611 */ 612 public void copyCatalog(Catalog catalog, Catalog catalogToCopy) throws ProcessingException 613 { 614 String catalogToCopyName = catalogToCopy.getName(); 615 String catalogName = catalog.getName(); 616 String [] handledEventIds = new String[] {ObservationConstants.EVENT_CONTENT_ADDED, ObservationConstants.EVENT_CONTENT_MODIFIED, ObservationConstants.EVENT_CONTENT_WORKFLOW_CHANGED}; 617 try 618 { 619 Map<String, String> copiedPrograms = new HashMap<>(); 620 Map<String, String> copiedSubPrograms = new HashMap<>(); 621 Map<String, String> copiedContainers = new HashMap<>(); 622 Map<String, String> copiedCourseLists = new HashMap<>(); 623 Map<String, String> copiedCourses = new HashMap<>(); 624 Map<String, String> copiedCourseParts = new HashMap<>(); 625 626 Set<String> copyUpdaters = _copyUpdaterEP.getExtensionsIds(); 627 628 AmetysObjectIterable<Program> programs = getPrograms(catalogToCopyName); 629 630 // Do NOT commit yet to Solr in order to improve perfs 631 _observationManager.addArgumentForEvents(handledEventIds, ObservationConstants.ARGS_CONTENT_COMMIT, false); 632 633 long start = System.currentTimeMillis(); 634 635 getLogger().debug("Begin to iterate over programs for copying them"); 636 637 for (Program program : programs) 638 { 639 if (getLogger().isDebugEnabled()) 640 { 641 getLogger().debug("Start copying program '{}' (name: '{}', title: '{}')...", program.getId(), program.getName(), program.getTitle()); 642 } 643 644 Program newProgram = _odfHelper.copyProgramItem(program, catalogName, true, copiedPrograms, copiedSubPrograms, copiedContainers, copiedCourseLists, copiedCourses, copiedCourseParts); 645 646 for (String updaterId : copyUpdaters) 647 { 648 // Call updaters after copy of program 649 CopyCatalogUpdater updater = _copyUpdaterEP.getExtension(updaterId); 650 updater.updateContent(catalogToCopyName, catalogName, program, newProgram); 651 } 652 } 653 654 for (String updaterId : copyUpdaters) 655 { 656 // Call updaters after full copy of catalog 657 CopyCatalogUpdater updater = _copyUpdaterEP.getExtension(updaterId); 658 updater.updateContents(catalogToCopyName, catalogName, copiedPrograms, copiedSubPrograms, copiedContainers, copiedCourseLists, copiedCourses, copiedCourseParts); 659 } 660 661 // Workflow 662 _addCopyStep(copiedPrograms.values()); 663 _addCopyStep(copiedSubPrograms.values()); 664 _addCopyStep(copiedContainers.values()); 665 _addCopyStep(copiedCourseLists.values()); 666 _addCopyStep(copiedCourses.values()); 667 _addCopyStep(copiedCourseParts.values()); 668 669 if (getLogger().isDebugEnabled()) 670 { 671 getLogger().debug("End of iteration over programs for copying them ({})", Duration.of((System.currentTimeMillis() - start) / 1000, ChronoUnit.SECONDS)); 672 } 673 674 } 675 catch (AmetysRepositoryException | WorkflowException e) 676 { 677 getLogger().error("Copy of items of catalog {} into catalog {} has failed", catalogToCopyName, catalogName); 678 throw new ProcessingException("Failed to copy catalog", e); 679 } 680 finally 681 { 682 _observationManager.removeArgumentForEvents(handledEventIds, ObservationConstants.ARGS_CONTENT_COMMIT); 683 684 // Before trying to commit, be sure all the async observers of the current request are finished 685 for (Future future : _observationManager.getFuturesForRequest()) 686 { 687 try 688 { 689 future.get(); 690 } 691 catch (ExecutionException | InterruptedException e) 692 { 693 getLogger().info("An exception occured when calling #get() on Future result of an observer." , e); 694 } 695 } 696 697 // Commit all uncommited changes 698 try 699 { 700 _solrIndexer.commit(); 701 702 getLogger().debug("Copied contents are now committed into Solr."); 703 } 704 catch (IOException | SolrServerException e) 705 { 706 getLogger().error("Impossible to commit changes", e); 707 } 708 } 709 } 710 711 private void _addCopyStep(Collection<String> contentIds) throws AmetysRepositoryException, WorkflowException 712 { 713 for (String contentId : contentIds) 714 { 715 WorkflowAwareContent content = _resolver.resolveById(contentId); 716 _contentWorkflowHelper.doAction(content, getCopyActionId()); 717 } 718 } 719 720 /** 721 * Get the workflow action id for copy. 722 * @return The workflow action id 723 */ 724 protected int getCopyActionId() 725 { 726 return 210; 727 } 728 729 /** 730 * Delete catalog 731 * @param id the id of catalog 732 */ 733 public void deleteCatalog (String id) 734 { 735 try 736 { 737 Catalog catalog = _resolver.resolveById(id); 738 if (catalog != null) 739 { 740 catalog.remove(); 741 catalog.saveChanges(); 742 } 743 } 744 catch (UnknownAmetysObjectException e) 745 { 746 // Nothing 747 } 748 749 } 750 751 private ModifiableTraversableAmetysObject _getOrCreateNode(ModifiableTraversableAmetysObject parentNode, String nodeName, String nodeType) throws AmetysRepositoryException 752 { 753 ModifiableTraversableAmetysObject definitionsNode; 754 if (parentNode.hasChild(nodeName)) 755 { 756 definitionsNode = parentNode.getChild(nodeName); 757 } 758 else 759 { 760 definitionsNode = parentNode.createChild(nodeName, nodeType); 761 parentNode.saveChanges(); 762 } 763 return definitionsNode; 764 } 765}