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.util.ArrayList; 019import java.util.Date; 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; 029 030import javax.jcr.Node; 031import javax.jcr.RepositoryException; 032 033import org.apache.avalon.framework.component.Component; 034import org.apache.avalon.framework.service.ServiceException; 035import org.apache.avalon.framework.service.ServiceManager; 036import org.apache.avalon.framework.service.Serviceable; 037import org.apache.commons.lang3.StringUtils; 038import org.apache.jackrabbit.util.Text; 039 040import org.ametys.cms.FilterNameHelper; 041import org.ametys.core.group.GroupIdentity; 042import org.ametys.core.group.GroupManager; 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.Query.QueryProfile; 052import org.ametys.plugins.queriesdirectory.Query.Visibility; 053import org.ametys.plugins.repository.AmetysObject; 054import org.ametys.plugins.repository.AmetysObjectIterable; 055import org.ametys.plugins.repository.AmetysObjectResolver; 056import org.ametys.plugins.repository.AmetysRepositoryException; 057import org.ametys.plugins.repository.ModifiableTraversableAmetysObject; 058import org.ametys.plugins.repository.MovableAmetysObject; 059import org.ametys.plugins.repository.UnknownAmetysObjectException; 060import org.ametys.runtime.plugin.component.AbstractLogEnabled; 061 062import com.google.common.base.Predicates; 063import com.google.common.collect.ImmutableMap; 064 065/** 066 * DAO for manipulating queries 067 */ 068public class QueryDAO extends AbstractLogEnabled implements Serviceable, Component 069{ 070 /** The Avalon role */ 071 public static final String ROLE = QueryDAO.class.getName(); 072 073 /** The alias id of the root {@link QueryContainer} */ 074 public static final String ROOT_QUERY_CONTAINER_ID = "root"; 075 076 private static final String __PLUGIN_NODE_NAME = "queriesdirectory"; 077 078 /** The current user provider */ 079 protected CurrentUserProvider _userProvider; 080 /** The group manager */ 081 protected GroupManager _groupManager; 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 _groupManager = (GroupManager) serviceManager.lookup(GroupManager.ROLE); 096 _userHelper = (UserHelper) serviceManager.lookup(UserHelper.ROLE); 097 _rightManager = (RightManager) serviceManager.lookup(RightManager.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 156 for (String id : queryIds) 157 { 158 try 159 { 160 Query query = _resolver.resolveById(id); 161 162 if (_hasRight(query)) 163 { 164 queries.add(getQueryProperties(query)); 165 } 166 else 167 { 168 notAllowedQueries.add(getQueryProperties(query)); 169 } 170 } 171 catch (UnknownAmetysObjectException e) 172 { 173 unknownQueries.add(id); 174 } 175 } 176 177 result.put("queries", queries); 178 result.put("unknownQueries", unknownQueries); 179 result.put("unknownQueries", unknownQueries); 180 181 return result; 182 } 183 184 /** 185 * Get the query properties 186 * @param query The query 187 * @return The query properties 188 */ 189 public Map<String, Object> getQueryProperties (Query query) 190 { 191 Map<String, Object> infos = new HashMap<>(); 192 193 infos.put("isQuery", true); 194 infos.put("id", query.getId()); 195 infos.put("title", query.getTitle()); 196 infos.put("type", query.getType()); 197 infos.put("description", query.getDescription()); 198 infos.put("author", _userHelper.user2json(query.getAuthor())); 199 infos.put("visibility", query.getVisibility().toString()); 200 infos.put("content", query.getContent()); 201 infos.put("lastModificationDate", DateUtils.dateToString(query.getLastModificationDate())); 202 203 boolean isAdministrator = isAdministrator(); 204 infos.put("isAdministrator", isAdministrator); 205 206 UserIdentity currentUser = _userProvider.getUser(); 207 infos.put("canRead", isAdministrator || query.canRead(currentUser)); 208 infos.put("canWrite", isAdministrator || query.canWrite(currentUser)); 209 210 return infos; 211 } 212 213 /** 214 * Gets the ids of the path elements of a query or query container, i.e. the parent ids. 215 * <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"] 216 * @param queryId The id of the query 217 * @return the ids of the path elements of a query 218 */ 219 @Callable 220 public List<String> getIdsOfPath(String queryId) 221 { 222 AmetysObject queryOrQueryContainer = _resolver.resolveById(queryId); 223 QueryContainer queriesRootNode = getQueriesRootNode(); 224 225 if (!(queryOrQueryContainer instanceof Query) && !(queryOrQueryContainer instanceof QueryContainer)) 226 { 227 throw new IllegalArgumentException("The given id is not a query nor a query container"); 228 } 229 230 List<String> pathElements = new ArrayList<>(); 231 QueryContainer current = queryOrQueryContainer.getParent(); 232 while (!queriesRootNode.equals(current)) 233 { 234 pathElements.add(0, current.getId()); 235 current = current.getParent(); 236 } 237 238 return pathElements; 239 } 240 241 /** 242 * Determines if the current user has right to handle {@link QueryContainer}s 243 * @return <code>true</code> if the current user has right to handle {@link QueryContainer}s 244 */ 245 @Callable 246 public boolean hasRightOnContainers() 247 { 248 return _rightManager.currentUserHasRight("QueriesDirectory_Rights_Containers", "/cms") == RightResult.RIGHT_ALLOW; 249 } 250 251 /** 252 * Get the query container properties 253 * @param id The query container id. Can be {@link #ROOT_QUERY_CONTAINER_ID} for the root container. 254 * @return The query container properties 255 */ 256 @Callable 257 public Map<String, Object> getQueryContainerProperties(String id) 258 { 259 return getQueryContainerProperties(_getQueryContainer(id)); 260 } 261 262 /** 263 * Get the query container properties 264 * @param queryContainer The query container 265 * @return The query container properties 266 */ 267 public Map<String, Object> getQueryContainerProperties(QueryContainer queryContainer) 268 { 269 Map<String, Object> infos = new HashMap<>(); 270 271 infos.put("isQuery", false); 272 infos.put("id", queryContainer.getId()); 273 infos.put("title", queryContainer.getName()); 274 275 return infos; 276 } 277 278 /** 279 * Creates a new {@link Query} 280 * @param title The title of the query 281 * @param desc The description of the query 282 * @param type The type of the query 283 * @param content The content of the query 284 * @param parentId The id of the parent of the query. Use {@link #ROOT_QUERY_CONTAINER_ID} for the root container. 285 * @return A result map 286 */ 287 @Callable 288 public Map<String, Object> createQuery(String title, String desc, String type, String content, String parentId) 289 { 290 Map<String, Object> results = new HashMap<>(); 291 292 QueryContainer queriesNode = _getQueryContainer(parentId); 293 294 String name = FilterNameHelper.filterName(title); 295 296 // Find unique name 297 String uniqueName = name; 298 int index = 2; 299 while (queriesNode.hasChild(uniqueName)) 300 { 301 uniqueName = name + "-" + (index++); 302 } 303 304 Query query = queriesNode.createChild(uniqueName, QueryFactory.QUERY_NODETYPE); 305 query.setTitle(title); 306 query.setDescription(desc); 307 query.setAuthor(_userProvider.getUser()); 308 query.setType(type); 309 query.setContent(content); 310 query.setVisibility(Visibility.PRIVATE); 311 query.setLastModificationDate(new Date()); 312 313 queriesNode.saveChanges(); 314 315 results.put("id", query.getId()); 316 317 return results; 318 } 319 320 /** 321 * Creates a new {@link QueryContainer} 322 * @param parentId The id of the parent. Use {@link #ROOT_QUERY_CONTAINER_ID} for the root container. 323 * @param name The desired name for the new {@link QueryContainer} 324 * @return A result map 325 */ 326 @Callable(right = "QueriesDirectory_Rights_Containers", context = "/cms") 327 public Map<String, Object> createQueryContainer(String parentId, String name) 328 { 329 Map<String, Object> results = new HashMap<>(); 330 331 QueryContainer parent = _getQueryContainer(parentId); 332 333 int index = 2; 334 String legalName = Text.escapeIllegalJcrChars(name); 335 String realName = legalName; 336 while (parent.hasChild(realName)) 337 { 338 realName = legalName + " (" + index + ")"; 339 index++; 340 } 341 342 QueryContainer createdChild = parent.createChild(realName, QueryContainerFactory.QUERY_CONTAINER_NODETYPE); 343 parent.saveChanges(); 344 345 results.put("id", createdChild.getId()); 346 results.put("name", realName); 347 348 return results; 349 } 350 351 /** 352 * Edits a {@link Query} 353 * @param id The id of the query 354 * @param title The title of the query 355 * @param desc The description of the query 356 * @return A result map 357 */ 358 @Callable 359 public Map<String, Object> updateQuery(String id, String title, String desc) 360 { 361 Map<String, Object> results = new HashMap<>(); 362 363 Query query = _resolver.resolveById(id); 364 365 if (query.canWrite(_userProvider.getUser())) 366 { 367 query.setTitle(title); 368 query.setDescription(desc); 369 query.saveChanges(); 370 } 371 else 372 { 373 results.put("message", "not-allowed"); 374 } 375 376 results.put("id", query.getId()); 377 378 return results; 379 } 380 381 /** 382 * Renames a {@link QueryContainer} 383 * @param id The id of the query container 384 * @param newName The new name of the container 385 * @return A result map 386 */ 387 @Callable(right = "QueriesDirectory_Rights_Containers", context = "/cms") 388 public Map<String, Object> renameQueryContainer(String id, String newName) 389 { 390 Map<String, Object> results = new HashMap<>(); 391 392 QueryContainer query = _resolver.resolveById(id); 393 394 String legalName = Text.escapeIllegalJcrChars(newName); 395 Node node = query.getNode(); 396 try 397 { 398 node.getSession().move(node.getPath(), node.getParent().getPath() + '/' + legalName); 399 node.getSession().save(); 400 401 results.put("id", id); 402 results.put("newName", legalName); 403 } 404 catch (RepositoryException e) 405 { 406 getLogger().warn("Query container renaming failed.", e); 407 results.put("message", "cannot-rename"); 408 } 409 410 return results; 411 } 412 413 /** 414 * Moves a {@link Query} 415 * @param id The id of the query 416 * @param newParentId The id of the new parent container of the query. Use {@link #ROOT_QUERY_CONTAINER_ID} for the root container. 417 * @return A result map 418 */ 419 @Callable 420 public Map<String, Object> moveQuery(String id, String newParentId) 421 { 422 Map<String, Object> results = new HashMap<>(); 423 Query query = _resolver.resolveById(id); 424 425 if (query.canWrite(_userProvider.getUser()) 426 || hasRightOnContainers()) 427 { 428 _move(query, newParentId, results); 429 } 430 else 431 { 432 results.put("message", "not-allowed"); 433 } 434 435 results.put("id", query.getId()); 436 return results; 437 } 438 439 /** 440 * Moves a {@link QueryContainer} 441 * @param id The id of the query container 442 * @param newParentId The id of the new parent container of the query container. Use {@link #ROOT_QUERY_CONTAINER_ID} for the root container. 443 * @return A result map 444 */ 445 @Callable(right = "QueriesDirectory_Rights_Containers", context = "/cms") 446 public Map<String, Object> moveQueryContainer(String id, String newParentId) 447 { 448 Map<String, Object> results = new HashMap<>(); 449 QueryContainer queryContainer = _resolver.resolveById(id); 450 451 _move(queryContainer, newParentId, results); 452 453 results.put("id", queryContainer.getId()); 454 results.put("name", queryContainer.getName()); 455 return results; 456 } 457 458 private void _move(MovableAmetysObject obj, String newParentId, Map<String, Object> results) 459 { 460 QueryContainer newParent = _getQueryContainer(newParentId); 461 if (obj.canMoveTo(newParent)) 462 { 463 try 464 { 465 obj.moveTo(newParent, false); 466 } 467 catch (AmetysRepositoryException e) 468 { 469 getLogger().warn("Query moving failed.", e); 470 results.put("message", "cannot-move"); 471 } 472 } 473 else 474 { 475 results.put("message", "cannot-move"); 476 } 477 } 478 479 private QueryContainer _getQueryContainer(String id) 480 { 481 QueryContainer container; 482 if (ROOT_QUERY_CONTAINER_ID.equals(id)) 483 { 484 container = getQueriesRootNode(); 485 } 486 else 487 { 488 container = _resolver.resolveById(id); 489 } 490 return container; 491 } 492 493 /** 494 * Saves a {@link Query} 495 * @param id The id of the query 496 * @param type The type of the query 497 * @param content The content of the query 498 * @return A result map 499 */ 500 @Callable 501 public Map<String, Object> saveQuery(String id, String type, String content) 502 { 503 Map<String, Object> results = new HashMap<>(); 504 505 Query query = _resolver.resolveById(id); 506 507 if (query.canWrite(_userProvider.getUser())) 508 { 509 query.setType(type); 510 query.setContent(content); 511 query.saveChanges(); 512 } 513 else 514 { 515 results.put("message", "not-allowed"); 516 } 517 518 results.put("id", query.getId()); 519 520 return results; 521 } 522 523 /** 524 * Deletes {@link Query}(ies) 525 * @param ids The ids of the queries to delete 526 * @return A result map 527 */ 528 @Callable 529 public Map<String, Object> deleteQuery(List<String> ids) 530 { 531 Map<String, Object> results = new HashMap<>(); 532 533 List<String> deletedQueries = new ArrayList<>(); 534 List<String> unknownQueries = new ArrayList<>(); 535 List<String> notallowedQueries = new ArrayList<>(); 536 537 for (String id : ids) 538 { 539 try 540 { 541 Query query = _resolver.resolveById(id); 542 543 UserIdentity author = query.getAuthor(); 544 if (author != null && author.equals(_userProvider.getUser())) 545 { 546 query.remove(); 547 query.saveChanges(); 548 deletedQueries.add(id); 549 } 550 else 551 { 552 notallowedQueries.add(query.getTitle()); 553 } 554 } 555 catch (UnknownAmetysObjectException e) 556 { 557 unknownQueries.add(id); 558 getLogger().error("Unable to delete query. The query of id '" + id + " doesn't exist", e); 559 } 560 } 561 562 results.put("deletedQueries", deletedQueries); 563 results.put("notallowedQueries", notallowedQueries); 564 results.put("unknownQueries", unknownQueries); 565 566 return results; 567 } 568 569 /** 570 * Can the current user delete all the {@link QueryContainer}s from the list ? 571 * @param ids The {@link QueryContainer} ids 572 * @return <code>true</code> if he can 573 */ 574 protected boolean canDeleteAllQueryContainers(List<String> ids) 575 { 576 return canDeleteQueryContainers(ids).values() 577 .stream() 578 .allMatch(Boolean.TRUE::equals); 579 } 580 581 /** 582 * Determines if the current user can delete the given {@link QueryContainer}s 583 * @param ids The {@link QueryContainer} ids 584 * @return A map with <code>true</code> for each id if the current user can delete the associated {@link QueryContainer} 585 */ 586 @Callable 587 public Map<String, Boolean> canDeleteQueryContainers(List<String> ids) 588 { 589 return ids.stream() 590 .collect(Collectors.toMap( 591 Function.identity(), 592 LambdaUtils.wrap(this::_canDeleteQueryContainer))); 593 } 594 595 private boolean _canDeleteQueryContainer(String id) 596 { 597 if (ROOT_QUERY_CONTAINER_ID.equals(id)) 598 { 599 return false; 600 } 601 else 602 { 603 UserIdentity user = _userProvider.getUser(); 604 Set<GroupIdentity> userGroups = _groupManager.getUserGroups(user); 605 QueryContainer container = _resolver.resolveById(id); 606 607 org.ametys.plugins.queriesdirectory.QueryHelper.Visibility visibility = org.ametys.plugins.queriesdirectory.QueryHelper.Visibility.of(user, userGroups); 608 AmetysObjectIterable<Query> queriesInWriteAccess = getChildQueriesInWriteAccess(container, false, visibility, Optional.empty()); 609 AmetysObjectIterable<Query> allQueries = getChildQueriesForAdministrator(container, false, Optional.empty()); 610 return allQueries.getSize() == queriesInWriteAccess.getSize(); // if different collections, then it means the current user has not the write access on at least one query 611 } 612 } 613 614 /** 615 * Determines if application must warn before deleting the given {@link QueryContainer}s 616 * @param ids The {@link QueryContainer} ids 617 * @return <code>true</code> if application must warn 618 */ 619 @Callable 620 public boolean mustWarnBeforeDeletion(List<String> ids) 621 { 622 return ids.stream() 623 .anyMatch(LambdaUtils.wrapPredicate(this::_mustWarnBeforeDeletion)); 624 } 625 626 private boolean _mustWarnBeforeDeletion(String id) 627 { 628 QueryContainer container = _resolver.resolveById(id); 629 AmetysObjectIterable<Query> allQueries = getChildQueriesForAdministrator(container, false, Optional.empty()); 630 return _containsNotOwnQueries(allQueries) || _containsSharedQueries(allQueries); 631 } 632 633 private boolean _containsNotOwnQueries(AmetysObjectIterable<Query> allQueries) 634 { 635 UserIdentity currentUser = _userProvider.getUser(); 636 return allQueries.stream() 637 .map(Query::getAuthor) 638 .anyMatch(Predicates.not(currentUser::equals)); 639 } 640 641 private boolean _containsSharedQueries(AmetysObjectIterable<Query> allQueries) 642 { 643 return allQueries.stream() 644 .map(Query::getVisibility) 645 .anyMatch(Predicates.not(Visibility.PRIVATE::equals)); 646 } 647 648 649 /** 650 * Deletes {@link QueryContainer}(s) 651 * @param ids The ids of the query containers to delete 652 * @return A result map 653 */ 654 @Callable(right = "QueriesDirectory_Rights_Containers", context = "/cms") 655 public Map<String, Object> deleteQueryContainer(List<String> ids) 656 { 657 Map<String, Object> results = new HashMap<>(); 658 659 List<Map<String, Object>> allDeleted = new ArrayList<>(); 660 List<Map<String, Object>> allUnknown = new ArrayList<>(); 661 662 if (!canDeleteAllQueryContainers(ids)) 663 { 664 results.put("message", "not-allowed"); 665 return results; 666 } 667 668 for (String id : ids) 669 { 670 try 671 { 672 QueryContainer container = _resolver.resolveById(id); 673 String name = container.getName(); 674 container.remove(); 675 container.saveChanges(); 676 Map<String, Object> deleted = ImmutableMap.of("id", id, "name", name); 677 allDeleted.add(deleted); 678 } 679 catch (UnknownAmetysObjectException e) 680 { 681 Map<String, Object> unknown = ImmutableMap.of("id", id, "name", id); 682 allUnknown.add(unknown); 683 getLogger().error("Unable to delete query container. The container of id '" + id + " doesn't exist", e); 684 } 685 } 686 687 results.put("deleted", allDeleted); 688 results.put("unknown", allUnknown); 689 690 return results; 691 } 692 693 /** 694 * Changes the visibility of a {@link Query} 695 * @param queryId The id of the query 696 * @param visibilityStr The new visibility 697 * @return A result map 698 */ 699 @Callable 700 public Map<String, Object> changeVisibility(String queryId, String visibilityStr) 701 { 702 Map<String, Object> results = new HashMap<>(); 703 704 // Parameter checks. 705 Query query = null; 706 if (StringUtils.isNotEmpty(queryId)) 707 { 708 query = _resolver.resolveById(queryId); 709 } 710 else 711 { 712 throw new IllegalArgumentException("Mandatory query id parameter is missing."); 713 } 714 715 Visibility visibility = Visibility.valueOf(visibilityStr.toUpperCase()); 716 717 UserIdentity author = query.getAuthor(); 718 if (author != null && author.equals(_userProvider.getUser())) 719 { 720 query.setVisibility(visibility); 721 query.saveChanges(); 722 } 723 else 724 { 725 results.put("message", "not-allowed"); 726 } 727 728 return results; 729 } 730 731 /** 732 * Assign rights to the given users on the given query 733 * @param queryId The query id 734 * @param profileId The profile id 735 * @param users The users to grant 736 * @return A result map 737 */ 738 @Callable 739 public Map<String, Object> addGrantedUsers(String queryId, String profileId, List<Map<String, String>> users) 740 { 741 return _assignRights(queryId, profileId, "users", users, null); 742 } 743 744 /** 745 * Assign rigths to the given groups on the given query 746 * @param queryId The query id 747 * @param profileId The profile id 748 * @param groups The groups to grant 749 * @return A result map 750 */ 751 @Callable 752 public Map<String, Object> addGrantedGroups(String queryId, String profileId, List<Map<String, String>> groups) 753 { 754 return _assignRights(queryId, profileId, "groups", null, groups); 755 } 756 757 private Map<String, Object> _assignRights(String queryId, String profileId, String type, List<Map<String, String>> users, List<Map<String, String>> groups) 758 { 759 HashMap<String, Object> results = new HashMap<>(); 760 761 // Parameter checks. 762 Query query = null; 763 if (StringUtils.isNotEmpty(queryId)) 764 { 765 query = _resolver.resolveById(queryId); 766 } 767 else 768 { 769 throw new IllegalArgumentException("Mandatory query id parameter is missing."); 770 } 771 772 if (!"users".equals(type) && !"groups".equals(type)) 773 { 774 throw new IllegalArgumentException("Unexpected type parameter : " + type); 775 } 776 777 if (!"read_access".equals(profileId) && !"write_access".equals(profileId)) 778 { 779 throw new IllegalArgumentException("Unexpected profile identifier : " + profileId); 780 } 781 782 UserIdentity author = query.getAuthor(); 783 if (author != null && author.equals(_userProvider.getUser())) 784 { 785 QueryProfile profile = QueryProfile.valueOf(profileId.toUpperCase()); 786 787 // Set new values 788 if ("users".equals(type)) 789 { 790 Set<UserIdentity> allEntries = query.getGrantedUsers(profile); 791 792 for (Map<String, String> user : users) 793 { 794 UserIdentity userIdentity = new UserIdentity(user.get("login"), user.get("populationId")); 795 if (!allEntries.contains(userIdentity)) 796 { 797 allEntries.add(userIdentity); 798 } 799 } 800 801 query.setGrantedUsers(profile, allEntries); 802 } 803 else 804 { 805 Set<GroupIdentity> allEntries = query.getGrantedGroups(profile); 806 807 for (Map<String, String> group : groups) 808 { 809 GroupIdentity groupIdentity = new GroupIdentity(group.get("id"), group.get("groupDirectory")); 810 if (!allEntries.contains(groupIdentity)) 811 { 812 allEntries.add(groupIdentity); 813 } 814 } 815 816 query.setGrantedGroups(profile, allEntries); 817 } 818 819 // Save 820 query.saveChanges(); 821 } 822 else 823 { 824 results.put("message", "not-allowed"); 825 } 826 827 return results; 828 } 829 830 /** 831 * Remove rights to the given users on the given query 832 * @param queryId The query id 833 * @param profileId The profile id 834 * @param users The users to remove 835 * @param groups The groups to remove 836 * @return A result map 837 */ 838 @Callable 839 public Map<String, Object> removeAssignment(String queryId, String profileId, List<Map<String, String>> users, List<Map<String, String>> groups) 840 { 841 HashMap<String, Object> results = new HashMap<>(); 842 843 // Parameter checks. 844 Query query = null; 845 if (StringUtils.isNotEmpty(queryId)) 846 { 847 query = _resolver.resolveById(queryId); 848 } 849 else 850 { 851 throw new IllegalArgumentException("Mandatory query id parameter is missing."); 852 } 853 if (!"read_access".equals(profileId) && !"write_access".equals(profileId)) 854 { 855 throw new IllegalArgumentException("Unexpected profile identifier : " + profileId); 856 } 857 858 UserIdentity author = query.getAuthor(); 859 if (author != null && author.equals(_userProvider.getUser())) 860 { 861 QueryProfile profile = QueryProfile.valueOf(profileId.toUpperCase()); 862 863 if (!users.isEmpty()) 864 { 865 Set<UserIdentity> grantedUsers = query.getGrantedUsers(profile); 866 for (Map<String, String> user : users) 867 { 868 UserIdentity userIdentity = new UserIdentity(user.get("login"), user.get("populationId")); 869 grantedUsers.remove(userIdentity); 870 } 871 872 query.setGrantedUsers(profile, grantedUsers); 873 } 874 875 if (!groups.isEmpty()) 876 { 877 Set<GroupIdentity> grantedGroups = query.getGrantedGroups(profile); 878 for (Map<String, String> group : groups) 879 { 880 GroupIdentity groupIdentity = new GroupIdentity(group.get("id"), group.get("groupDirectory")); 881 grantedGroups.remove(groupIdentity); 882 } 883 884 query.setGrantedGroups(profile, grantedGroups); 885 } 886 887 // Save 888 query.saveChanges(); 889 } 890 else 891 { 892 results.put("message", "not-allowed"); 893 } 894 895 return results; 896 } 897 898 /** 899 * Determines if the current user is administrator of queries 900 * @return <code>true</code> if current user is administrator 901 */ 902 public boolean isAdministrator() 903 { 904 return _rightManager.hasRight(_userProvider.getUser(), "QueriesDirectory_Rights_Admin", "/cms") == RightResult.RIGHT_ALLOW; 905 } 906 907 /** 908 * Test if the current user has the right required to access the query 909 * @param query The query 910 * @return true if the user has the right needed, false otherwise. 911 */ 912 protected boolean _hasRight(Query query) 913 { 914 UserIdentity user = _userProvider.getUser(); 915 return query.canRead(user) || isAdministrator(); 916 } 917 918 /** 919 * Gets all queries for administrator for given parent 920 * @param parent The {@link QueryContainer}, defining the context from which getting children 921 * @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 922 * @param type The query type 923 * @return all queries for administrator for given parent 924 */ 925 public AmetysObjectIterable<Query> getChildQueriesForAdministrator(QueryContainer parent, boolean onlyDirect, Optional<String> type) 926 { 927 return _resolver.query(QueryHelper.getXPathForQueriesForAdministrator(parent, onlyDirect, type)); 928 } 929 930 /** 931 * Gets all queries in READ access for given parent 932 * @param parent The {@link QueryContainer}, defining the context from which getting children 933 * @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 934 * @param visibility The user and its groups for checking visibility 935 * @param type The query type 936 * @return all queries in READ access for given parent 937 */ 938 public AmetysObjectIterable<Query> getChildQueriesInReadAccess(QueryContainer parent, boolean onlyDirect, QueryHelper.Visibility visibility, Optional<String> type) 939 { 940 return _resolver.query(QueryHelper.getXPathForQueriesInReadAccess(parent, onlyDirect, visibility, type)); 941 } 942 943 /** 944 * Gets all queries in WRITE access for given parent 945 * @param parent The {@link QueryContainer}, defining the context from which getting children 946 * @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 947 * @param visibility The user and its groups for checking visibility 948 * @param type The query type 949 * @return all queries in WRITE access for given parent 950 */ 951 public AmetysObjectIterable<Query> getChildQueriesInWriteAccess(QueryContainer parent, boolean onlyDirect, QueryHelper.Visibility visibility, Optional<String> type) 952 { 953 return _resolver.query(QueryHelper.getXPathForQueriesInWriteAccess(parent, onlyDirect, visibility, type)); 954 } 955 956 /** 957 * Gets all query containers for given parent 958 * @param parent The {@link QueryContainer}, defining the context from which getting children 959 * @return all query containers for given parent 960 */ 961 public AmetysObjectIterable<QueryContainer> getChildQueryContainers(QueryContainer parent) 962 { 963 return _resolver.query(QueryHelper.getXPathForQueryContainers(parent)); 964 } 965}