001/* 002 * Copyright 2016 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.queriesdirectory; 017 018import java.time.ZonedDateTime; 019import java.util.ArrayList; 020import java.util.HashMap; 021import java.util.HashSet; 022import java.util.LinkedList; 023import java.util.List; 024import java.util.Map; 025import java.util.Optional; 026import java.util.Set; 027import java.util.function.Function; 028import java.util.stream.Collectors; 029import java.util.stream.Stream; 030 031import javax.jcr.Node; 032import javax.jcr.RepositoryException; 033 034import org.apache.avalon.framework.component.Component; 035import org.apache.avalon.framework.service.ServiceException; 036import org.apache.avalon.framework.service.ServiceManager; 037import org.apache.avalon.framework.service.Serviceable; 038import org.apache.jackrabbit.util.Text; 039 040import org.ametys.cms.FilterNameHelper; 041import org.ametys.core.observation.Event; 042import org.ametys.core.observation.ObservationManager; 043import org.ametys.core.right.RightManager; 044import org.ametys.core.right.RightManager.RightResult; 045import org.ametys.core.ui.Callable; 046import org.ametys.core.user.CurrentUserProvider; 047import org.ametys.core.user.UserIdentity; 048import org.ametys.core.util.DateUtils; 049import org.ametys.core.util.LambdaUtils; 050import org.ametys.plugins.core.user.UserHelper; 051import org.ametys.plugins.queriesdirectory.observation.ObservationConstants; 052import org.ametys.plugins.repository.AmetysObject; 053import org.ametys.plugins.repository.AmetysObjectIterable; 054import org.ametys.plugins.repository.AmetysObjectResolver; 055import org.ametys.plugins.repository.AmetysRepositoryException; 056import org.ametys.plugins.repository.ModifiableTraversableAmetysObject; 057import org.ametys.plugins.repository.MovableAmetysObject; 058import org.ametys.plugins.repository.UnknownAmetysObjectException; 059import org.ametys.runtime.plugin.component.AbstractLogEnabled; 060 061import com.google.common.base.Predicates; 062import com.google.common.collect.ImmutableMap; 063 064/** 065 * DAO for manipulating queries 066 */ 067public class QueryDAO extends AbstractLogEnabled implements Serviceable, Component 068{ 069 /** The Avalon role */ 070 public static final String ROLE = QueryDAO.class.getName(); 071 072 /** The alias id of the root {@link QueryContainer} */ 073 public static final String ROOT_QUERY_CONTAINER_ID = "root"; 074 075 private static final String __PLUGIN_NODE_NAME = "queriesdirectory"; 076 077 /** The current user provider */ 078 protected CurrentUserProvider _userProvider; 079 080 /** The observation manager */ 081 protected ObservationManager _observationManager; 082 083 /** The Ametys object resolver */ 084 private AmetysObjectResolver _resolver; 085 086 private UserHelper _userHelper; 087 088 private RightManager _rightManager; 089 090 @Override 091 public void service(ServiceManager serviceManager) throws ServiceException 092 { 093 _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE); 094 _userProvider = (CurrentUserProvider) serviceManager.lookup(CurrentUserProvider.ROLE); 095 _userHelper = (UserHelper) serviceManager.lookup(UserHelper.ROLE); 096 _rightManager = (RightManager) serviceManager.lookup(RightManager.ROLE); 097 _observationManager = (ObservationManager) serviceManager.lookup(ObservationManager.ROLE); 098 } 099 100 /** 101 * Get the root plugin storage object. 102 * @return the root plugin storage object. 103 * @throws AmetysRepositoryException if a repository error occurs. 104 */ 105 public QueryContainer getQueriesRootNode() throws AmetysRepositoryException 106 { 107 try 108 { 109 return _getOrCreateRootNode(); 110 } 111 catch (AmetysRepositoryException e) 112 { 113 throw new AmetysRepositoryException("Unable to get the queries root node", e); 114 } 115 } 116 117 private QueryContainer _getOrCreateRootNode() throws AmetysRepositoryException 118 { 119 ModifiableTraversableAmetysObject pluginsNode = _resolver.resolveByPath("/ametys:plugins"); 120 121 ModifiableTraversableAmetysObject pluginNode = (ModifiableTraversableAmetysObject) _getOrCreateNode(pluginsNode, __PLUGIN_NODE_NAME, "ametys:unstructured"); 122 123 return (QueryContainer) _getOrCreateNode(pluginNode, "ametys:queries", QueryContainerFactory.QUERY_CONTAINER_NODETYPE); 124 } 125 126 private static AmetysObject _getOrCreateNode(ModifiableTraversableAmetysObject parentNode, String nodeName, String nodeType) throws AmetysRepositoryException 127 { 128 AmetysObject definitionsNode; 129 if (parentNode.hasChild(nodeName)) 130 { 131 definitionsNode = parentNode.getChild(nodeName); 132 } 133 else 134 { 135 definitionsNode = parentNode.createChild(nodeName, nodeType); 136 parentNode.saveChanges(); 137 } 138 return definitionsNode; 139 } 140 141 /** 142 * Get queries' properties 143 * @param queryIds The ids of queries to retrieve 144 * @return The queries' properties 145 */ 146 @Callable 147 public Map<String, Object> getQueriesProperties(List<String> queryIds) 148 { 149 Map<String, Object> result = new HashMap<>(); 150 151 List<Map<String, Object>> queries = new LinkedList<>(); 152 List<Map<String, Object>> notAllowedQueries = new LinkedList<>(); 153 Set<String> unknownQueries = new HashSet<>(); 154 155 UserIdentity user = _userProvider.getUser(); 156 157 for (String id : queryIds) 158 { 159 try 160 { 161 Query query = _resolver.resolveById(id); 162 163 if (this.hasReadRightOnQuery(user, query)) 164 { 165 queries.add(getQueryProperties(query)); 166 } 167 else 168 { 169 notAllowedQueries.add(getQueryProperties(query)); 170 } 171 } 172 catch (UnknownAmetysObjectException e) 173 { 174 unknownQueries.add(id); 175 } 176 } 177 178 result.put("queries", queries); 179 result.put("unknownQueries", unknownQueries); 180 result.put("unknownQueries", unknownQueries); 181 182 return result; 183 } 184 185 /** 186 * Get the query properties 187 * @param query The query 188 * @return The query properties 189 */ 190 public Map<String, Object> getQueryProperties (Query query) 191 { 192 Map<String, Object> infos = new HashMap<>(); 193 194 List<String> fullPath = new ArrayList<>(); 195 fullPath.add(query.getTitle()); 196 197 AmetysObject node = query.getParent(); 198 while (node instanceof QueryContainer 199 && node.getParent() instanceof QueryContainer) // The parent must also be a container to avoid the root 200 { 201 fullPath.add(0, node.getName()); 202 node = node.getParent(); 203 } 204 205 206 infos.put("isQuery", true); 207 infos.put("id", query.getId()); 208 infos.put("title", query.getTitle()); 209 infos.put("fullPath", String.join(" > ", fullPath)); 210 infos.put("type", query.getType()); 211 infos.put("description", query.getDescription()); 212 infos.put("documentation", query.getDocumentation()); 213 infos.put("author", _userHelper.user2json(query.getAuthor())); 214 infos.put("contributor", _userHelper.user2json(query.getContributor())); 215 infos.put("content", query.getContent()); 216 infos.put("lastModificationDate", DateUtils.zonedDateTimeToString(query.getLastModificationDate())); 217 infos.put("creationDate", DateUtils.zonedDateTimeToString(query.getCreationDate())); 218 219 UserIdentity currentUser = _userProvider.getUser(); 220 infos.put("canRead", this.hasReadRightOnQuery(currentUser, query)); 221 infos.put("canWrite", this.hasWriteRightOnQuery(currentUser, query)); 222 infos.put("canEditRight", this.hasRightAffectationRightOnQuery(currentUser, query)); 223 224 return infos; 225 } 226 227 /** 228 * Gets the ids of the path elements of a query or query container, i.e. the parent ids. 229 * <br>For instance, if the query path is 'a/b/c', then the result list will be ["id-of-a", "id-of-b", "id-of-c"] 230 * @param queryId The id of the query 231 * @return the ids of the path elements of a query 232 */ 233 @Callable 234 public List<String> getIdsOfPath(String queryId) 235 { 236 AmetysObject queryOrQueryContainer = _resolver.resolveById(queryId); 237 QueryContainer queriesRootNode = getQueriesRootNode(); 238 239 if (!(queryOrQueryContainer instanceof Query) && !(queryOrQueryContainer instanceof QueryContainer)) 240 { 241 throw new IllegalArgumentException("The given id is not a query nor a query container"); 242 } 243 244 List<String> pathElements = new ArrayList<>(); 245 QueryContainer current = queryOrQueryContainer.getParent(); 246 while (!queriesRootNode.equals(current)) 247 { 248 pathElements.add(0, current.getId()); 249 current = current.getParent(); 250 } 251 252 return pathElements; 253 } 254 255 /** 256 * Get the root container properties 257 * @return The root container properties 258 */ 259 @Callable 260 public Map<String, Object> getRootProperties() 261 { 262 return getQueryContainerProperties(getQueriesRootNode()); 263 } 264 265 /** 266 * Get the query container properties 267 * @param id The query container id. Can be {@link #ROOT_QUERY_CONTAINER_ID} for the root container. 268 * @return The query container properties 269 */ 270 @Callable 271 public Map<String, Object> getQueryContainerProperties(String id) 272 { 273 return getQueryContainerProperties(_getQueryContainer(id)); 274 } 275 276 /** 277 * Get the query container properties 278 * @param queryContainer The query container 279 * @return The query container properties 280 */ 281 public Map<String, Object> getQueryContainerProperties(QueryContainer queryContainer) 282 { 283 Map<String, Object> infos = new HashMap<>(); 284 285 infos.put("isQuery", false); 286 infos.put("id", queryContainer.getId()); 287 infos.put("title", queryContainer.getName()); 288 infos.put("fullPath", _getFullPath(queryContainer)); 289 290 UserIdentity currentUser = _userProvider.getUser(); 291 boolean canRead = hasReadRightOnQueryContainer(currentUser, queryContainer); 292 boolean canWrite = hasWriteRightOnQueryContainer(currentUser, queryContainer); 293 boolean canWriteParent = queryContainer.getParent() instanceof QueryContainer && hasWriteRightOnQueryContainer(currentUser, (QueryContainer) queryContainer.getParent()); 294 295 // Used to check if a user have the right to rename this container 296 infos.put("canRename", canWrite && canWriteParent); 297 298 // Used to check if a user have the right to add containers or queries inside this container 299 infos.put("canWrite", canWrite); 300 301 // Used to check if a user have the right to delete and drag&drop this container 302 infos.put("canEdit", canWrite && _hasWriteRightOnEachDescendant(currentUser, queryContainer) && canWriteParent); 303 304 // Used to check if a user have the right to limit access to this container 305 infos.put("canEditRight", hasRightAffectationRightOnQueryContainer(currentUser, queryContainer)); 306 307 // Used to filter containers 308 infos.put("displayForRead", canRead || canWrite || hasAnyReadableDescendant(currentUser, queryContainer) || hasAnyWritableDescendant(currentUser, queryContainer)); 309 infos.put("displayForWrite", canWrite || _hasAnyWriteDescendantFolder(currentUser, queryContainer)); 310 infos.put("displayForRights", canWrite || hasAnyAssignableDescendant(currentUser, queryContainer)); 311 312 return infos; 313 } 314 315 private String _getFullPath(QueryContainer queryContainer) 316 { 317 List<String> fullPath = new ArrayList<>(); 318 fullPath.add(queryContainer.getName()); 319 320 AmetysObject node = queryContainer.getParent(); 321 while (node instanceof QueryContainer 322 && node.getParent() instanceof QueryContainer) // The parent must also be a container to avoid the root 323 { 324 fullPath.add(0, node.getName()); 325 node = node.getParent(); 326 } 327 return String.join(" > ", fullPath); 328 } 329 330 /** 331 * Creates a new {@link Query} 332 * @param title The title of the query 333 * @param desc The description of the query 334 * @param documentation The documentation of the query 335 * @param type The type of the query 336 * @param content The content of the query 337 * @param parentId The id of the parent of the query. Use {@link #ROOT_QUERY_CONTAINER_ID} for the root container. 338 * @return A result map 339 */ 340 @Callable 341 public Map<String, Object> createQuery(String title, String desc, String documentation, String type, String content, String parentId) 342 { 343 Map<String, Object> results = new HashMap<>(); 344 345 QueryContainer queriesNode = _getQueryContainer(parentId); 346 347 if (!this.hasWriteRightOnQueryContainer(_userProvider.getUser(), queriesNode)) 348 { 349 results.put("message", "not-allowed"); 350 return results; 351 } 352 353 String name = FilterNameHelper.filterName(title); 354 355 // Find unique name 356 String uniqueName = name; 357 int index = 2; 358 while (queriesNode.hasChild(uniqueName)) 359 { 360 uniqueName = name + "-" + (index++); 361 } 362 363 Query query = queriesNode.createChild(uniqueName, QueryFactory.QUERY_NODETYPE); 364 query.setTitle(title); 365 query.setDescription(desc); 366 query.setDocumentation(documentation); 367 query.setAuthor(_userProvider.getUser()); 368 query.setContributor(_userProvider.getUser()); 369 query.setType(type); 370 query.setContent(content); 371 query.setCreationDate(ZonedDateTime.now()); 372 query.setLastModificationDate(ZonedDateTime.now()); 373 374 queriesNode.saveChanges(); 375 376 results.put("id", query.getId()); 377 results.put("title", query.getTitle()); 378 results.put("content", query.getContent()); 379 380 return results; 381 } 382 383 /** 384 * Creates a new {@link QueryContainer} 385 * @param parentId The id of the parent. Use {@link #ROOT_QUERY_CONTAINER_ID} for the root container. 386 * @param name The desired name for the new {@link QueryContainer} 387 * @return A result map 388 */ 389 @Callable 390 public Map<String, Object> createQueryContainer(String parentId, String name) 391 { 392 393 QueryContainer parent = _getQueryContainer(parentId); 394 395 if (!this.hasWriteRightOnQueryContainer(_userProvider.getUser(), parent)) 396 { 397 return ImmutableMap.of("message", "not-allowed"); 398 } 399 400 int index = 2; 401 String legalName = Text.escapeIllegalJcrChars(name); 402 String realName = legalName; 403 while (parent.hasChild(realName)) 404 { 405 realName = legalName + " (" + index + ")"; 406 index++; 407 } 408 409 QueryContainer createdChild = parent.createChild(realName, QueryContainerFactory.QUERY_CONTAINER_NODETYPE); 410 parent.saveChanges(); 411 412 Map<String, Object> results = this.getQueryContainerProperties(createdChild); 413 results.put("name", realName); 414 415 return results; 416 } 417 418 /** 419 * Edits a {@link Query} 420 * @param id The id of the query 421 * @param title The title of the query 422 * @param desc The description of the query 423 * @param documentation The documentation of the query 424 * @return A result map 425 */ 426 @Callable 427 public Map<String, Object> updateQuery(String id, String title, String desc, String documentation) 428 { 429 Map<String, Object> results = new HashMap<>(); 430 431 Query query = _resolver.resolveById(id); 432 433 if (this.hasWriteRightOnQuery(_userProvider.getUser(), query)) 434 { 435 query.setTitle(title); 436 query.setDescription(desc); 437 query.setDocumentation(documentation); 438 query.setContributor(_userProvider.getUser()); 439 query.setLastModificationDate(ZonedDateTime.now()); 440 query.saveChanges(); 441 } 442 else 443 { 444 results.put("message", "not-allowed"); 445 } 446 447 results.put("id", query.getId()); 448 results.put("title", query.getTitle()); 449 450 return results; 451 } 452 453 /** 454 * Renames a {@link QueryContainer} 455 * @param id The id of the query container 456 * @param newName The new name of the container 457 * @return A result map 458 */ 459 @Callable 460 public Map<String, Object> renameQueryContainer(String id, String newName) 461 { 462 QueryContainer query = _resolver.resolveById(id); 463 464 Map<String, Object> results = getQueryContainerProperties(query); 465 466 467 UserIdentity currentUser = _userProvider.getUser(); 468 469 boolean canWrite = hasWriteRightOnQueryContainer(currentUser, query); 470 boolean canWriteParent = query.getParent() instanceof QueryContainer && hasWriteRightOnQueryContainer(currentUser, (QueryContainer) query.getParent()); 471 472 if (canWrite && canWriteParent) 473 { 474 String legalName = Text.escapeIllegalJcrChars(newName); 475 Node node = query.getNode(); 476 try 477 { 478 node.getSession().move(node.getPath(), node.getParent().getPath() + '/' + legalName); 479 node.getSession().save(); 480 481 results.put("id", id); 482 results.put("newName", legalName); 483 } 484 catch (RepositoryException e) 485 { 486 getLogger().warn("Query container renaming failed.", e); 487 results.put("message", "cannot-rename"); 488 } 489 } 490 else 491 { 492 results.put("message", "not-allowed"); 493 } 494 495 return results; 496 } 497 498 /** 499 * Moves a {@link Query} 500 * @param id The id of the query 501 * @param newParentId The id of the new parent container of the query. Use {@link #ROOT_QUERY_CONTAINER_ID} for the root container. 502 * @return A result map 503 */ 504 @Callable 505 public Map<String, Object> moveQuery(String id, String newParentId) 506 { 507 Map<String, Object> results = new HashMap<>(); 508 Query query = _resolver.resolveById(id); 509 QueryContainer queryContainer = _resolver.resolveById(newParentId); 510 511 if (this.hasWriteRightOnQuery(_userProvider.getUser(), query) && this.hasWriteRightOnQueryContainer(_userProvider.getUser(), queryContainer)) 512 { 513 _move(query, newParentId, results); 514 } 515 else 516 { 517 results.put("message", "not-allowed"); 518 } 519 520 results.put("id", query.getId()); 521 return results; 522 } 523 524 /** 525 * Moves a {@link QueryContainer} 526 * @param id The id of the query container 527 * @param newParentId The id of the new parent container of the query container. Use {@link #ROOT_QUERY_CONTAINER_ID} for the root container. 528 * @return A result map 529 */ 530 @Callable 531 public Map<String, Object> moveQueryContainer(String id, String newParentId) 532 { 533 QueryContainer queryContainer = _resolver.resolveById(id); 534 QueryContainer parentQueryContainer = _resolver.resolveById(newParentId); 535 UserIdentity currentUser = _userProvider.getUser(); 536 537 Map<String, Object> results = this.getQueryContainerProperties(queryContainer); 538 539 if (_hasWriteRightOnEachDescendant(currentUser, queryContainer) && hasWriteRightOnQueryContainer(currentUser, parentQueryContainer)) 540 { 541 _move(queryContainer, newParentId, results); 542 } 543 else 544 { 545 results.put("message", "not-allowed"); 546 } 547 548 results.put("name", queryContainer.getName()); 549 return results; 550 } 551 552 private void _move(MovableAmetysObject obj, String newParentId, Map<String, Object> results) 553 { 554 QueryContainer newParent = _getQueryContainer(newParentId); 555 if (obj.canMoveTo(newParent)) 556 { 557 try 558 { 559 obj.moveTo(newParent, false); 560 } 561 catch (AmetysRepositoryException e) 562 { 563 getLogger().warn("Query moving failed.", e); 564 results.put("message", "cannot-move"); 565 } 566 } 567 else 568 { 569 results.put("message", "cannot-move"); 570 } 571 } 572 573 private QueryContainer _getQueryContainer(String id) 574 { 575 QueryContainer container; 576 if (ROOT_QUERY_CONTAINER_ID.equals(id)) 577 { 578 container = getQueriesRootNode(); 579 } 580 else 581 { 582 container = _resolver.resolveById(id); 583 } 584 return container; 585 } 586 587 /** 588 * Saves a {@link Query} 589 * @param id The id of the query 590 * @param type The type of the query 591 * @param content The content of the query 592 * @return A result map 593 */ 594 @Callable 595 public Map<String, Object> saveQuery(String id, String type, String content) 596 { 597 Map<String, Object> results = new HashMap<>(); 598 599 Query query = _resolver.resolveById(id); 600 UserIdentity user = _userProvider.getUser(); 601 if (this.hasWriteRightOnQuery(user, query)) 602 { 603 query.setType(type); 604 query.setContent(content); 605 query.setContributor(user); 606 query.setLastModificationDate(ZonedDateTime.now()); 607 query.saveChanges(); 608 } 609 else 610 { 611 results.put("message", "not-allowed"); 612 } 613 614 results.put("id", query.getId()); 615 results.put("title", query.getTitle()); 616 results.put("content", query.getContent()); 617 618 return results; 619 } 620 621 /** 622 * Deletes {@link Query}(ies) 623 * @param ids The ids of the queries to delete 624 * @return A result map 625 */ 626 @Callable 627 public Map<String, Object> deleteQuery(List<String> ids) 628 { 629 Map<String, Object> results = new HashMap<>(); 630 631 List<String> deletedQueries = new ArrayList<>(); 632 List<String> unknownQueries = new ArrayList<>(); 633 List<String> notallowedQueries = new ArrayList<>(); 634 635 for (String id : ids) 636 { 637 try 638 { 639 Query query = _resolver.resolveById(id); 640 641 if (this.hasWriteRightOnQuery(_userProvider.getUser(), query)) 642 { 643 Map<String, Object> params = new HashMap<>(); 644 params.put(ObservationConstants.ARGS_QUERY_ID, query.getId()); 645 646 query.remove(); 647 query.saveChanges(); 648 deletedQueries.add(id); 649 650 _observationManager.notify(new Event(ObservationConstants.EVENT_QUERY_DELETED, _userProvider.getUser(), params)); 651 } 652 else 653 { 654 notallowedQueries.add(query.getTitle()); 655 } 656 } 657 catch (UnknownAmetysObjectException e) 658 { 659 unknownQueries.add(id); 660 getLogger().error("Unable to delete query. The query of id '" + id + " doesn't exist", e); 661 } 662 } 663 664 results.put("deletedQueries", deletedQueries); 665 results.put("notallowedQueries", notallowedQueries); 666 results.put("unknownQueries", unknownQueries); 667 668 return results; 669 } 670 671 /** 672 * Can the current user delete all the {@link QueryContainer}s from the list ? 673 * @param ids The {@link QueryContainer} ids 674 * @return <code>true</code> if he can 675 */ 676 protected boolean canDeleteAllQueryContainers(List<String> ids) 677 { 678 return canDeleteQueryContainers(ids).values() 679 .stream() 680 .allMatch(Boolean.TRUE::equals); 681 } 682 683 /** 684 * Determines if the current user can delete the given {@link QueryContainer}s 685 * @param ids The {@link QueryContainer} ids 686 * @return A map with <code>true</code> for each id if the current user can delete the associated {@link QueryContainer} 687 */ 688 @Callable 689 public Map<String, Boolean> canDeleteQueryContainers(List<String> ids) 690 { 691 return ids.stream() 692 .collect(Collectors.toMap( 693 Function.identity(), 694 LambdaUtils.wrap(this::_canDeleteQueryContainer))); 695 } 696 697 private boolean _canDeleteQueryContainer(String id) 698 { 699 if (ROOT_QUERY_CONTAINER_ID.equals(id)) 700 { 701 return false; 702 } 703 else 704 { 705 UserIdentity user = _userProvider.getUser(); 706 QueryContainer container = _resolver.resolveById(id); 707 708 return _hasWriteRightOnEachDescendant(user, container) && container.getParent() instanceof QueryContainer && hasWriteRightOnQueryContainer(user, (QueryContainer) container.getParent()); 709 } 710 } 711 712 /** 713 * Determines if application must warn before deleting the given {@link QueryContainer}s 714 * @param ids The {@link QueryContainer} ids 715 * @return <code>true</code> if application must warn 716 */ 717 @Callable 718 public boolean mustWarnBeforeDeletion(List<String> ids) 719 { 720 return ids.stream() 721 .anyMatch(LambdaUtils.wrapPredicate(this::_mustWarnBeforeDeletion)); 722 } 723 724 private boolean _mustWarnBeforeDeletion(String id) 725 { 726 QueryContainer container = _resolver.resolveById(id); 727 AmetysObjectIterable<Query> allQueries = getChildQueriesForAdministrator(container, false, Optional.empty()); 728 return _containsNotOwnQueries(allQueries); 729 } 730 731 private boolean _containsNotOwnQueries(AmetysObjectIterable<Query> allQueries) 732 { 733 UserIdentity currentUser = _userProvider.getUser(); 734 return allQueries.stream() 735 .map(Query::getAuthor) 736 .anyMatch(Predicates.not(currentUser::equals)); 737 } 738 739 740 /** 741 * Deletes {@link QueryContainer}(s) 742 * @param ids The ids of the query containers to delete 743 * @return A result map 744 */ 745 @Callable 746 public Map<String, Object> deleteQueryContainer(List<String> ids) 747 { 748 Map<String, Object> results = new HashMap<>(); 749 750 List<Map<String, Object>> allDeleted = new ArrayList<>(); 751 List<Map<String, Object>> allUnknown = new ArrayList<>(); 752 753 if (!canDeleteAllQueryContainers(ids)) 754 { 755 results.put("message", "not-allowed"); 756 return results; 757 } 758 759 for (String id : ids) 760 { 761 try 762 { 763 QueryContainer container = _resolver.resolveById(id); 764 Map<String, Object> deleted = getQueryContainerProperties(container); 765 deleted.put("name", container.getName()); 766 container.remove(); 767 container.saveChanges(); 768 allDeleted.add(deleted); 769 } 770 catch (UnknownAmetysObjectException e) 771 { 772 Map<String, Object> unknown = ImmutableMap.of("id", id, "name", id); 773 allUnknown.add(unknown); 774 getLogger().error("Unable to delete query container. The container of id '" + id + " doesn't exist", e); 775 } 776 } 777 778 results.put("deleted", allDeleted); 779 results.put("unknown", allUnknown); 780 781 return results; 782 } 783 784 /** 785 * Gets all queries for administrator for given parent 786 * @param parent The {@link QueryContainer}, defining the context from which getting children 787 * @param onlyDirect <code>true</code> in order to have only direct child queries from parent path, <code>false</code> otherwise to have all queries at any level underneath the parent path 788 * @param type The query type 789 * @return all queries for administrator for given parent 790 */ 791 public AmetysObjectIterable<Query> getChildQueriesForAdministrator(QueryContainer parent, boolean onlyDirect, Optional<String> type) 792 { 793 return _resolver.query(QueryHelper.getXPathForQueriesForAdministrator(parent, onlyDirect, type)); 794 } 795 796 /** 797 * Gets all queries in READ access for given parent 798 * @param parent The {@link QueryContainer}, defining the context from which getting children 799 * @param onlyDirect <code>true</code> in order to have only direct child queries from parent path, <code>false</code> otherwise to have all queries at any level underneath the parent path 800 * @param user The user 801 * @param type The query type 802 * @return all queries in READ access for given parent 803 */ 804 public Stream<Query> getChildQueriesInReadAccess(QueryContainer parent, boolean onlyDirect, UserIdentity user, Optional<String> type) 805 { 806 return _resolver.query(QueryHelper.getXPathForQueries(parent, onlyDirect, type)) 807 .stream() 808 .filter(Query.class::isInstance) 809 .map(obj -> (Query) obj) 810 .filter(query -> hasReadRightOnQuery(user, query)); 811 } 812 813 /** 814 * Check if a user have read rights on a query 815 * @param userIdentity the user 816 * @param query the query 817 * @return true if the user have read rights on a query 818 */ 819 public boolean hasReadRightOnQuery(UserIdentity userIdentity, Query query) 820 { 821 return query != null && (userIdentity.equals(query.getAuthor()) || _rightManager.hasReadAccess(userIdentity, query)); 822 } 823 824 /** 825 * Check if a user have read rights on a query container 826 * @param userIdentity the user 827 * @param query the query container 828 * @return true if the user have read rights on a query container 829 */ 830 public boolean hasReadRightOnQueryContainer(UserIdentity userIdentity, QueryContainer query) 831 { 832 return _rightManager.hasReadAccess(userIdentity, query); 833 } 834 835 /** 836 * Gets all queries in WRITE access for given parent 837 * @param parent The {@link QueryContainer}, defining the context from which getting children 838 * @param onlyDirect <code>true</code> in order to have only direct child queries from parent path, <code>false</code> otherwise to have all queries at any level underneath the parent path 839 * @param user The user 840 * @param type The query type 841 * @return all queries in WRITE access for given parent 842 */ 843 public Stream<Query> getChildQueriesInWriteAccess(QueryContainer parent, boolean onlyDirect, UserIdentity user, Optional<String> type) 844 { 845 return _resolver.query(QueryHelper.getXPathForQueries(parent, onlyDirect, type)) 846 .stream() 847 .filter(Query.class::isInstance) 848 .map(obj -> (Query) obj) 849 .filter(query -> hasWriteRightOnQuery(user, query)); 850 } 851 852 /** 853 * Check if a user have write rights on a query 854 * @param userIdentity the user 855 * @param query the query 856 * @return true if the user have write rights on a query 857 */ 858 public boolean hasWriteRightOnQuery(UserIdentity userIdentity, Query query) 859 { 860 return query != null && (userIdentity.equals(query.getAuthor()) || _rightManager.hasRight(userIdentity, "QueriesDirectory_Rights_Admin", query) == RightResult.RIGHT_ALLOW && hasWriteRightOnQueryContainer(userIdentity, query.getParent())); 861 } 862 863 /** 864 * Gets all queries in WRITE access for given parent 865 * @param parent The {@link QueryContainer}, defining the context from which getting children 866 * @param onlyDirect <code>true</code> in order to have only direct child queries from parent path, <code>false</code> otherwise to have all queries at any level underneath the parent path 867 * @param user The user 868 * @param type The query type 869 * @return all queries in WRITE access for given parent 870 */ 871 public Stream<Query> getChildQueriesInRightAccess(QueryContainer parent, boolean onlyDirect, UserIdentity user, Optional<String> type) 872 { 873 return _resolver.query(QueryHelper.getXPathForQueries(parent, onlyDirect, type)) 874 .stream() 875 .filter(Query.class::isInstance) 876 .map(obj -> (Query) obj) 877 .filter(query -> hasRightAffectationRightOnQuery(user, query)); 878 } 879 880 /** 881 * Check if a user have write rights on a query 882 * @param userIdentity the user 883 * @param query the query 884 * @return true if the user have write rights on a query 885 */ 886 public boolean hasRightAffectationRightOnQuery(UserIdentity userIdentity, Query query) 887 { 888 return hasWriteRightOnQuery(userIdentity, query) || _rightManager.hasRight(userIdentity, "Runtime_Rights_Rights_Handle", "/cms") == RightResult.RIGHT_ALLOW; 889 } 890 891 /** 892 * Check if a user have creation rights on a query container 893 * @param userIdentity the user 894 * @param queryContainer the query container 895 * @return true if the user have creation rights on a query container 896 */ 897 public boolean hasCreationRightOnQueryContainer(UserIdentity userIdentity, QueryContainer queryContainer) 898 { 899 return _rightManager.hasRight(userIdentity, "QueriesDirectory_Rights_Containers", queryContainer) == RightResult.RIGHT_ALLOW || _rightManager.hasRight(userIdentity, "QueriesDirectory_Rights_Containers", "/cms") == RightResult.RIGHT_ALLOW; 900 } 901 902 903 /** 904 * Check if a user have write rights on a query container 905 * @param userIdentity the user 906 * @param queryContainer the query container 907 * @return true if the user have write rights on a query container 908 */ 909 public boolean hasWriteRightOnQueryContainer(UserIdentity userIdentity, QueryContainer queryContainer) 910 { 911 return _rightManager.hasRight(userIdentity, "QueriesDirectory_Rights_Containers", queryContainer) == RightResult.RIGHT_ALLOW; 912 } 913 914 /** 915 * Check if a user have write rights on a query container 916 * @param userIdentity the user 917 * @param queryContainer the query container 918 * @return true if the user have write rights on a query 919 */ 920 public boolean hasRightAffectationRightOnQueryContainer(UserIdentity userIdentity, QueryContainer queryContainer) 921 { 922 return hasWriteRightOnQueryContainer(userIdentity, queryContainer) || _rightManager.hasRight(userIdentity, "Runtime_Rights_Rights_Handle", "/cms") == RightResult.RIGHT_ALLOW; 923 } 924 925 /** 926 * Gets all query containers for given parent 927 * @param parent The {@link QueryContainer}, defining the context from which getting children 928 * @return all query containers for given parent 929 */ 930 public AmetysObjectIterable<QueryContainer> getChildQueryContainers(QueryContainer parent) 931 { 932 return _resolver.query(QueryHelper.getXPathForQueryContainers(parent)); 933 } 934 935 /** 936 * Check if a folder have a descendant in read access for a given user 937 * @param userIdentity the user 938 * @param queryContainer the query container 939 * @return true if the user have read right for at least one child of this container 940 */ 941 public Boolean hasAnyReadableDescendant(UserIdentity userIdentity, QueryContainer queryContainer) 942 { 943 boolean hasDescendant = hasReadRightOnQueryContainer(userIdentity, queryContainer); 944 if (hasDescendant) 945 { 946 return true; 947 } 948 949 try (AmetysObjectIterable<AmetysObject> children = queryContainer.getChildren()) 950 { 951 for (AmetysObject child : children) 952 { 953 if (child instanceof QueryContainer) 954 { 955 hasDescendant = hasDescendant || hasAnyReadableDescendant(userIdentity, (QueryContainer) child); 956 } 957 else if (child instanceof Query) 958 { 959 hasDescendant = hasDescendant || hasReadRightOnQuery(userIdentity, (Query) child); 960 } 961 962 if (hasDescendant) 963 { 964 return true; 965 } 966 } 967 968 return hasDescendant; 969 } 970 } 971 972 /** 973 * Check if a folder have descendant in write access for a given user 974 * @param userIdentity the user 975 * @param queryContainer the query container 976 * @return true if the user have write right for at least one child of this container 977 */ 978 public Boolean hasAnyWritableDescendant(UserIdentity userIdentity, QueryContainer queryContainer) 979 { 980 boolean hasDescendant = hasWriteRightOnQueryContainer(userIdentity, queryContainer); 981 if (hasDescendant) 982 { 983 return true; 984 } 985 986 try (AmetysObjectIterable<AmetysObject> children = queryContainer.getChildren()) 987 { 988 for (AmetysObject child : children) 989 { 990 if (child instanceof QueryContainer) 991 { 992 hasDescendant = hasDescendant || hasAnyWritableDescendant(userIdentity, (QueryContainer) child); 993 } 994 else if (child instanceof Query) 995 { 996 hasDescendant = hasDescendant || hasWriteRightOnQueryAndParent(userIdentity, (Query) child, queryContainer); 997 } 998 999 if (hasDescendant) 1000 { 1001 return true; 1002 } 1003 } 1004 } 1005 1006 1007 return hasDescendant; 1008 } 1009 1010 private Boolean _hasAnyWriteDescendantFolder(UserIdentity userIdentity, QueryContainer queryContainer) 1011 { 1012 boolean hasDescendant = hasWriteRightOnQueryContainer(userIdentity, queryContainer); 1013 if (hasDescendant) 1014 { 1015 return true; 1016 } 1017 1018 try (AmetysObjectIterable<AmetysObject> children = queryContainer.getChildren()) 1019 { 1020 for (AmetysObject child : children) 1021 { 1022 if (child instanceof QueryContainer) 1023 { 1024 hasDescendant = hasDescendant || _hasAnyWriteDescendantFolder(userIdentity, (QueryContainer) child); 1025 } 1026 1027 if (hasDescendant) 1028 { 1029 return true; 1030 } 1031 } 1032 1033 return hasDescendant; 1034 } 1035 } 1036 1037 /** 1038 * Check if a folder have descendant in right assignment access for a given user 1039 * @param userIdentity the user 1040 * @param queryContainer the query container 1041 * @return true if the user have right assignment right for at least one child of this container 1042 */ 1043 public Boolean hasAnyAssignableDescendant(UserIdentity userIdentity, QueryContainer queryContainer) 1044 { 1045 boolean hasDescendant = hasRightAffectationRightOnQueryContainer(userIdentity, queryContainer); 1046 if (hasDescendant) 1047 { 1048 return true; 1049 } 1050 1051 try (AmetysObjectIterable<AmetysObject> children = queryContainer.getChildren()) 1052 { 1053 for (AmetysObject child : children) 1054 { 1055 if (child instanceof QueryContainer) 1056 { 1057 hasDescendant = hasDescendant || hasAnyAssignableDescendant(userIdentity, (QueryContainer) child); 1058 } 1059 else if (child instanceof Query) 1060 { 1061 hasDescendant = hasDescendant || hasRightAffectationRightOnQuery(userIdentity, (Query) child); 1062 } 1063 1064 if (hasDescendant) 1065 { 1066 return true; 1067 } 1068 } 1069 1070 return hasDescendant; 1071 } 1072 } 1073 1074 /** 1075 * Check if a user have write rights on an query container and each of his descendant 1076 * @param userIdentity the user 1077 * @param queryContainer the query container 1078 * @return true if the user have write rights on an query container and each of his descendant 1079 */ 1080 private boolean _hasWriteRightOnEachDescendant(UserIdentity userIdentity, QueryContainer queryContainer) 1081 { 1082 boolean hasRight = hasWriteRightOnQueryContainer(userIdentity, queryContainer); 1083 if (!hasRight) 1084 { 1085 return false; 1086 } 1087 1088 try (AmetysObjectIterable<AmetysObject> children = queryContainer.getChildren()) 1089 { 1090 for (AmetysObject child : children) 1091 { 1092 if (child instanceof QueryContainer) 1093 { 1094 hasRight = hasRight && hasWriteRightOnQueryContainer(userIdentity, (QueryContainer) child); 1095 } 1096 else if (child instanceof Query) 1097 { 1098 hasRight = hasRight && hasWriteRightOnQueryAndParent(userIdentity, (Query) child, queryContainer); 1099 } 1100 1101 if (!hasRight) 1102 { 1103 return false; 1104 } 1105 } 1106 1107 return hasRight; 1108 } 1109 } 1110 1111 /** 1112 * Check if a user have write rights on a query 1113 * @param userIdentity the user 1114 * @param query the source for the query 1115 * @param queryContainer the query container 1116 * @return true if the user have write rights on an query 1117 */ 1118 public boolean hasWriteRightOnQueryAndParent(UserIdentity userIdentity, Query query, QueryContainer queryContainer) 1119 { 1120 return query != null && userIdentity.equals(query.getAuthor()) || hasWriteRightOnQuery(userIdentity, query) && hasWriteRightOnQueryContainer(userIdentity, queryContainer); 1121 } 1122}