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