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