From 703c5e40cbace8bb3abe6bf80625d50f2ca73f11 Mon Sep 17 00:00:00 2001 From: Alessandro Ricchiuti <1532479+axl8713@users.noreply.github.com> Date: Thu, 6 Feb 2025 10:08:48 +0100 Subject: [PATCH] Support for Tag backend. (#396) * Implemented CRUD operation for tags. * Implemented tag association to the resource. * Added test for tag management. * Added log messages. * Added DB creation scripts for tag tables. * Added javadoc to tag REST service. * Refactoring. * Added count in get all tags operation result. * Added filtering by tag for the extJS get all operation. * Exposed get all tags operation to anonymous users. * Refactored nameLike path variable handling. * Introduced AssociatedEntityFilter abstraction. * Set distinct in resource count query. * Added null check to nameLike path variable handling. * Refactored nameLike query variable handling. * Added DB migration scripts. * Handled on delete cascade actions on tags. * Fixed hash calculation for StoredData to avoid stack overflow. * Fixed bug that was removing tag association after updates. * Signalled failing assertion in rest client test. * Fixed tag list serialization when null. * Handled names in filters that contain commas. --- doc/sql/002_create_schema_oracle.sql | 25 + doc/sql/002_create_schema_postgres.sql | 25 + .../h2-migration-from-v.2.1.0-to-v2.3.0.sql | 25 + ...racle-migration-from-v.2.1.0-to-v2.3.0.sql | 25 + ...resql-migration-from-v.2.1.0-to-v2.3.0.sql | 25 + .../geostore/core/model/Resource.java | 53 ++- .../geostore/core/model/StoredData.java | 2 +- .../geosolutions/geostore/core/model/Tag.java | 189 ++++++++ .../geostore/core/dao/TagDAO.java | 24 + .../geostore/core/dao/impl/TagDAOImpl.java | 75 +++ .../META-INF/geostore-persistence.xml | 1 + .../src/main/resources/applicationContext.xml | 4 +- .../resources/securityapplicationContext.xml | 6 +- .../src/test/resources/hibernate.cfg.xml | 1 + .../geostore/services/ResourceService.java | 6 +- .../geostore/services/TagService.java | 80 ++++ .../dto/ResourceSearchParameters.java | 14 + .../services/dto/search/AndFilter.java | 3 +- .../dto/search/AssociatedEntityFilter.java | 118 +++++ .../services/dto/search/FilterVisitor.java | 4 +- .../services/dto/search/GroupFilter.java | 72 +-- .../services/dto/search/OrFilter.java | 3 +- .../services/dto/search/TagFilter.java | 38 ++ .../services/ResourceServiceImpl.java | 38 +- .../geostore/services/TagServiceImpl.java | 163 +++++++ .../geostore/util/SearchConverter.java | 30 +- .../src/main/resources/applicationContext.xml | 25 +- .../geostore/services/ServiceTestBase.java | 29 ++ .../geostore/services/TagServiceImplTest.java | 216 +++++++++ .../services/rest/RESTTagService.java | 148 ++++++ .../geostore/services/rest/model/TagList.java | 58 +++ .../rest/utils/GeoStoreJAXBContext.java | 1 + .../services/rest/GeoStoreClientTest.java | 1 + .../geostore/services/model/ExtResource.java | 1 + .../services/model/ExtResourceList.java | 5 - .../services/model/ExtShortResource.java | 13 + .../services/rest/RESTExtJsService.java | 8 +- .../rest/impl/RESTExtJsServiceImpl.java | 59 ++- .../rest/impl/RESTExtJsServiceImplTest.java | 437 +++++++++++++++--- .../services/rest/impl/ServiceTestBase.java | 20 +- .../rest/impl/RESTCategoryServiceImpl.java | 3 +- .../rest/impl/RESTResourceServiceImpl.java | 29 +- .../services/rest/impl/RESTServiceImpl.java | 13 +- .../rest/impl/RESTTagServiceImpl.java | 134 ++++++ .../rest/impl/RESTUserServiceImpl.java | 16 +- .../src/main/resources/applicationContext.xml | 4 + .../service/impl/RESTTagServiceImplTest.java | 73 +++ 47 files changed, 2075 insertions(+), 267 deletions(-) create mode 100644 doc/sql/migration/h2/h2-migration-from-v.2.1.0-to-v2.3.0.sql create mode 100644 doc/sql/migration/oracle/oracle-migration-from-v.2.1.0-to-v2.3.0.sql create mode 100644 doc/sql/migration/postgresql/postgresql-migration-from-v.2.1.0-to-v2.3.0.sql create mode 100644 src/core/model/src/main/java/it/geosolutions/geostore/core/model/Tag.java create mode 100644 src/core/persistence/src/main/java/it/geosolutions/geostore/core/dao/TagDAO.java create mode 100644 src/core/persistence/src/main/java/it/geosolutions/geostore/core/dao/impl/TagDAOImpl.java create mode 100644 src/core/services-api/src/main/java/it/geosolutions/geostore/services/TagService.java create mode 100644 src/core/services-api/src/main/java/it/geosolutions/geostore/services/dto/search/AssociatedEntityFilter.java create mode 100644 src/core/services-api/src/main/java/it/geosolutions/geostore/services/dto/search/TagFilter.java create mode 100644 src/core/services-impl/src/main/java/it/geosolutions/geostore/services/TagServiceImpl.java create mode 100644 src/core/services-impl/src/test/java/it/geosolutions/geostore/services/TagServiceImplTest.java create mode 100644 src/modules/rest/api/src/main/java/it/geosolutions/geostore/services/rest/RESTTagService.java create mode 100644 src/modules/rest/api/src/main/java/it/geosolutions/geostore/services/rest/model/TagList.java create mode 100644 src/modules/rest/impl/src/main/java/it/geosolutions/geostore/services/rest/impl/RESTTagServiceImpl.java create mode 100644 src/modules/rest/impl/src/test/java/it/geosolutions/geostore/rest/service/impl/RESTTagServiceImplTest.java diff --git a/doc/sql/002_create_schema_oracle.sql b/doc/sql/002_create_schema_oracle.sql index 1da10b71..e6a297ef 100644 --- a/doc/sql/002_create_schema_oracle.sql +++ b/doc/sql/002_create_schema_oracle.sql @@ -108,6 +108,31 @@ foreign key (group_id) references gs_usergroup; + create table gs_tag ( + id number(19,0) not null, + color varchar2(255 char) not null, + description varchar2(255 char), + "name" varchar2(255 char) not null, + primary key (id) + ); + + create table gs_resource_tags ( + tag_id number(19,0) not null, + resource_id number(19,0) not null, + primary key (tag_id, resource_id) + ); + + alter table gs_resource_tags + add constraint fk_resource_tags_resource + foreign key (resource_id) + references gs_resource(id) + on delete cascade; + + alter table gs_resource_tags + add constraint fk_resource_tags_tag + foreign key (tag_id) + references gs_tag(id); + create index idx_attribute_name on gs_attribute (name); create index idx_attribute_resource on gs_attribute (resource_id); diff --git a/doc/sql/002_create_schema_postgres.sql b/doc/sql/002_create_schema_postgres.sql index bf2c36ac..46829095 100644 --- a/doc/sql/002_create_schema_postgres.sql +++ b/doc/sql/002_create_schema_postgres.sql @@ -125,6 +125,31 @@ SET search_path TO geostore; foreign key (group_id) references gs_usergroup; + create table gs_tag ( + id int8 not null, + color varchar(255) not null, + description varchar(255) null, + "name" varchar(255) not null, + constraint gs_tag_pkey primary key (id) + ); + + create table gs_resource_tags ( + tag_id int8 not null, + resource_id int8 not null, + constraint gs_resource_tags_pkey primary key (tag_id, resource_id) + ); + + alter table gs_resource_tags + add constraint fk_resource_tags_resource + foreign key (resource_id) + references gs_resource(id) + on delete cascade; + + alter table gs_resource_tags + add constraint fk_resource_tags_tag + foreign key (tag_id) + references gs_tag(id); + create index idx_attribute_name on gs_attribute (name); create index idx_attribute_resource on gs_attribute (resource_id); diff --git a/doc/sql/migration/h2/h2-migration-from-v.2.1.0-to-v2.3.0.sql b/doc/sql/migration/h2/h2-migration-from-v.2.1.0-to-v2.3.0.sql new file mode 100644 index 00000000..2d9ceec5 --- /dev/null +++ b/doc/sql/migration/h2/h2-migration-from-v.2.1.0-to-v2.3.0.sql @@ -0,0 +1,25 @@ +CREATE TABLE gs_tag ( + id BIGINT NOT NULL, + color VARCHAR(255) NOT NULL, + description VARCHAR(255), + name VARCHAR(255) NOT NULL, + CONSTRAINT gs_tag_pkey PRIMARY KEY (id) +); + +CREATE TABLE gs_resource_tags ( + tag_id BIGINT NOT NULL, + resource_id BIGINT NOT NULL, + CONSTRAINT gs_resource_tags_pkey PRIMARY KEY (tag_id, resource_id) +); + +-- Add foreign key constraints to gs_resource_tags +ALTER TABLE gs_resource_tags + ADD CONSTRAINT fk_resource_tags_resource + FOREIGN KEY (resource_id) + REFERENCES gs_resource(id) + ON DELETE CASCADE; + +ALTER TABLE gs_resource_tags + ADD CONSTRAINT fk_resource_tags_tag + FOREIGN KEY (tag_id) + REFERENCES gs_tag(id); \ No newline at end of file diff --git a/doc/sql/migration/oracle/oracle-migration-from-v.2.1.0-to-v2.3.0.sql b/doc/sql/migration/oracle/oracle-migration-from-v.2.1.0-to-v2.3.0.sql new file mode 100644 index 00000000..ff4601a4 --- /dev/null +++ b/doc/sql/migration/oracle/oracle-migration-from-v.2.1.0-to-v2.3.0.sql @@ -0,0 +1,25 @@ +create table gs_tag ( + id number(19,0) not null, + color varchar2(255 char) not null, + description varchar2(255 char), + "name" varchar2(255 char) not null, + primary key (id) +); + +create table gs_resource_tags ( + tag_id number(19,0) not null, + resource_id number(19,0) not null, + primary key (tag_id, resource_id) +); + +-- Add foreign key constraints to gs_resource_tags +alter table gs_resource_tags + add constraint fk_resource_tags_resource + foreign key (resource_id) + references gs_resource(id) + on delete cascade; + +alter table gs_resource_tags + add constraint fk_resource_tags_tag + foreign key (tag_id) + references gs_tag(id); \ No newline at end of file diff --git a/doc/sql/migration/postgresql/postgresql-migration-from-v.2.1.0-to-v2.3.0.sql b/doc/sql/migration/postgresql/postgresql-migration-from-v.2.1.0-to-v2.3.0.sql new file mode 100644 index 00000000..955ee088 --- /dev/null +++ b/doc/sql/migration/postgresql/postgresql-migration-from-v.2.1.0-to-v2.3.0.sql @@ -0,0 +1,25 @@ +create table gs_tag ( + id int8 not null, + color varchar(255) not null, + description varchar(255) null, + "name" varchar(255) not null, + constraint gs_tag_pkey primary key (id) +); + +create table gs_resource_tags ( + tag_id int8 not null, + resource_id int8 not null, + constraint gs_resource_tags_pkey primary key (tag_id, resource_id) +); + +-- Add foreign key constraints to gs_resource_tags +alter table gs_resource_tags + add constraint fk_resource_tags_resource + foreign key (resource_id) + references gs_resource(id) + on delete cascade; + +alter table gs_resource_tags + add constraint fk_resource_tags_tag + foreign key (tag_id) + references gs_tag(id); \ No newline at end of file diff --git a/src/core/model/src/main/java/it/geosolutions/geostore/core/model/Resource.java b/src/core/model/src/main/java/it/geosolutions/geostore/core/model/Resource.java index 782ee017..40bd41c2 100644 --- a/src/core/model/src/main/java/it/geosolutions/geostore/core/model/Resource.java +++ b/src/core/model/src/main/java/it/geosolutions/geostore/core/model/Resource.java @@ -32,6 +32,7 @@ import java.io.Serializable; import java.util.Date; import java.util.List; +import java.util.Set; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; @@ -39,6 +40,7 @@ import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Index; +import javax.persistence.ManyToMany; import javax.persistence.ManyToOne; import javax.persistence.OneToMany; import javax.persistence.OneToOne; @@ -49,6 +51,8 @@ import javax.xml.bind.annotation.XmlElementWrapper; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlTransient; +import org.hibernate.annotations.OnDelete; +import org.hibernate.annotations.OnDeleteAction; /** * Class Resource. @@ -119,6 +123,10 @@ public class Resource implements Serializable, CycleRecoverable { @OneToMany(mappedBy = "resource", cascade = CascadeType.REMOVE, fetch = FetchType.LAZY) private List security; + @ManyToMany(mappedBy = "resources", fetch = FetchType.EAGER) + @OnDelete(action = OnDeleteAction.CASCADE) + private Set tags; + /** @return the id */ public Long getId() { return id; @@ -169,6 +177,26 @@ public void setLastUpdate(Date lastUpdate) { this.lastUpdate = lastUpdate; } + /** @return the creator username */ + public String getCreator() { + return creator; + } + + /** @param creator the creator username */ + public void setCreator(String creator) { + this.creator = creator; + } + + /** @return the editor username */ + public String getEditor() { + return editor; + } + + /** @param editor the creator username */ + public void setEditor(String editor) { + this.editor = editor; + } + /** @return the advertised */ public Boolean isAdvertised() { return advertised; @@ -232,24 +260,12 @@ public void setSecurity(List security) { this.security = security; } - /** @return the creator username */ - public String getCreator() { - return creator; - } - - /** @param creator the creator username */ - public void setCreator(String creator) { - this.creator = creator; - } - - /** @return the editor username */ - public String getEditor() { - return editor; + public Set getTags() { + return tags; } - /** @param editor the creator username */ - public void setEditor(String editor) { - this.editor = editor; + public void setTags(Set tags) { + this.tags = tags; } /* @@ -314,6 +330,11 @@ public String toString() { builder.append("advertised=").append(advertised); } + if (tags != null) { + builder.append(", "); + builder.append("tags=").append(tags); + } + builder.append(']'); return builder.toString(); diff --git a/src/core/model/src/main/java/it/geosolutions/geostore/core/model/StoredData.java b/src/core/model/src/main/java/it/geosolutions/geostore/core/model/StoredData.java index 5c011615..36abb109 100644 --- a/src/core/model/src/main/java/it/geosolutions/geostore/core/model/StoredData.java +++ b/src/core/model/src/main/java/it/geosolutions/geostore/core/model/StoredData.java @@ -123,7 +123,7 @@ public int hashCode() { int result = 1; result = (prime * result) + ((data == null) ? 0 : data.hashCode()); result = (prime * result) + ((id == null) ? 0 : id.hashCode()); - result = (prime * result) + ((resource == null) ? 0 : resource.hashCode()); + result = (int) ((prime * result) + ((resource == null) ? 0 : resource.getId())); return result; } diff --git a/src/core/model/src/main/java/it/geosolutions/geostore/core/model/Tag.java b/src/core/model/src/main/java/it/geosolutions/geostore/core/model/Tag.java new file mode 100644 index 00000000..e09ee27f --- /dev/null +++ b/src/core/model/src/main/java/it/geosolutions/geostore/core/model/Tag.java @@ -0,0 +1,189 @@ +/* + * ==================================================================== + * + * Copyright (C) 2007 - 2012 GeoSolutions S.A.S. + * http://www.geo-solutions.it + * + * GPLv3 + Classpath exception + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. + * + * ==================================================================== + * + * This software consists of voluntary contributions made by developers + * of GeoSolutions. For more information on GeoSolutions, please see + * . + * + */ +package it.geosolutions.geostore.core.model; + +import java.io.Serializable; +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.Table; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlTransient; + +@Entity(name = "Tag") +@Table(name = "gs_tag") +@XmlRootElement(name = "Tag") +public class Tag implements Serializable { + + private static final long serialVersionUID = -6161770040532770363L; + + @Id @GeneratedValue private Long id; + + @Column(name = "name", nullable = false) + private String name; + + @Column(name = "color", nullable = false) + private String color; + + @Column(name = "description") + private String description; + + @ManyToMany + @JoinTable( + name = "gs_resource_tags", + joinColumns = @JoinColumn(name = "tag_id"), + inverseJoinColumns = @JoinColumn(name = "resource_id")) + private Set resources = new HashSet<>(); + + public Tag() {} + + public Tag(String name, String color, String description) { + this.name = name; + this.color = color; + this.description = description; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getColor() { + return color; + } + + public void setColor(String color) { + this.color = color; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + @XmlTransient + public Set getResources() { + return resources; + } + + public void setResources(Set resources) { + this.resources = resources; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(getClass().getSimpleName()).append('['); + + if (id != null) { + builder.append("id=").append(id); + } + + if (name != null) { + builder.append(", "); + builder.append("name=").append(name); + } + + if (color != null) { + builder.append(", "); + builder.append("color=").append(color); + } + + if (description != null) { + builder.append(", "); + builder.append("description=").append(description); + } + + builder.append(']'); + + return builder.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = (prime * result) + ((id == null) ? 0 : id.hashCode()); + result = (prime * result) + ((name == null) ? 0 : name.hashCode()); + result = (prime * result) + ((color == null) ? 0 : color.hashCode()); + result = (prime * result) + ((description == null) ? 0 : description.hashCode()); + + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + + Tag other = (Tag) obj; + if (id == null) { + if (other.id != null) { + return false; + } + } else if (!id.equals(other.id)) { + return false; + } + if (name == null) { + if (other.name != null) { + return false; + } + } else if (!name.equals(other.name)) { + return false; + } + return true; + } +} diff --git a/src/core/persistence/src/main/java/it/geosolutions/geostore/core/dao/TagDAO.java b/src/core/persistence/src/main/java/it/geosolutions/geostore/core/dao/TagDAO.java new file mode 100644 index 00000000..21ae6976 --- /dev/null +++ b/src/core/persistence/src/main/java/it/geosolutions/geostore/core/dao/TagDAO.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2007 - 2011 GeoSolutions S.A.S. + * http://www.geo-solutions.it + * + * GPLv3 + Classpath exception + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package it.geosolutions.geostore.core.dao; + +import it.geosolutions.geostore.core.model.Tag; + +public interface TagDAO extends RestrictedGenericDAO {} diff --git a/src/core/persistence/src/main/java/it/geosolutions/geostore/core/dao/impl/TagDAOImpl.java b/src/core/persistence/src/main/java/it/geosolutions/geostore/core/dao/impl/TagDAOImpl.java new file mode 100644 index 00000000..2a6056f3 --- /dev/null +++ b/src/core/persistence/src/main/java/it/geosolutions/geostore/core/dao/impl/TagDAOImpl.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2025 GeoSolutions S.A.S. + * http://www.geo-solutions.it + * + * GPLv3 + Classpath exception + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package it.geosolutions.geostore.core.dao.impl; + +import com.googlecode.genericdao.search.ISearch; +import it.geosolutions.geostore.core.dao.TagDAO; +import it.geosolutions.geostore.core.model.Tag; +import java.util.List; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.transaction.annotation.Transactional; + +@Transactional(value = "geostoreTransactionManager") +public class TagDAOImpl extends BaseDAO implements TagDAO { + + private static final Logger LOGGER = LogManager.getLogger(TagDAOImpl.class); + + @Override + public void persist(Tag... entities) { + if (LOGGER.isDebugEnabled()) { + LOGGER.info("Inserting new entities for Tag ... "); + } + + super.persist(entities); + } + + @Override + public List findAll() { + return super.findAll(); + } + + @SuppressWarnings("unchecked") + @Override + public List search(ISearch search) { + return super.search(search); + } + + @Override + public Tag merge(Tag entity) { + return super.merge(entity); + } + + @Override + public boolean remove(Tag entity) { + return super.remove(entity); + } + + @Override + public boolean removeById(Long id) { + return super.removeById(id); + } + + @Override + public int count(ISearch search) { + return super.count(search); + } +} diff --git a/src/core/persistence/src/main/resources/META-INF/geostore-persistence.xml b/src/core/persistence/src/main/resources/META-INF/geostore-persistence.xml index 0d030037..a75d4354 100644 --- a/src/core/persistence/src/main/resources/META-INF/geostore-persistence.xml +++ b/src/core/persistence/src/main/resources/META-INF/geostore-persistence.xml @@ -34,5 +34,6 @@ it.geosolutions.geostore.core.model.User it.geosolutions.geostore.core.model.UserGroup it.geosolutions.geostore.core.model.UserGroupAttribute + it.geosolutions.geostore.core.model.Tag diff --git a/src/core/persistence/src/main/resources/applicationContext.xml b/src/core/persistence/src/main/resources/applicationContext.xml index 0384f6ae..d3770872 100644 --- a/src/core/persistence/src/main/resources/applicationContext.xml +++ b/src/core/persistence/src/main/resources/applicationContext.xml @@ -70,8 +70,10 @@ + + + - diff --git a/src/core/persistence/src/main/resources/securityapplicationContext.xml b/src/core/persistence/src/main/resources/securityapplicationContext.xml index d033b475..55f497e2 100644 --- a/src/core/persistence/src/main/resources/securityapplicationContext.xml +++ b/src/core/persistence/src/main/resources/securityapplicationContext.xml @@ -69,7 +69,11 @@ - + + + + + diff --git a/src/core/persistence/src/test/resources/hibernate.cfg.xml b/src/core/persistence/src/test/resources/hibernate.cfg.xml index c7d25931..a2bb5f96 100644 --- a/src/core/persistence/src/test/resources/hibernate.cfg.xml +++ b/src/core/persistence/src/test/resources/hibernate.cfg.xml @@ -17,5 +17,6 @@ + diff --git a/src/core/services-api/src/main/java/it/geosolutions/geostore/services/ResourceService.java b/src/core/services-api/src/main/java/it/geosolutions/geostore/services/ResourceService.java index 0a1d05a3..1a0662a5 100644 --- a/src/core/services-api/src/main/java/it/geosolutions/geostore/services/ResourceService.java +++ b/src/core/services-api/src/main/java/it/geosolutions/geostore/services/ResourceService.java @@ -87,7 +87,7 @@ long insert(Resource resource) /** * @param id - * @return long + * @return the Resource or null if none was found with given id */ Resource get(long id); @@ -95,9 +95,11 @@ long insert(Resource resource) * @param id * @param includeAttributes * @param includePermissions + * @param includeTags * @return long */ - Resource getResource(long id, boolean includeAttributes, boolean includePermissions); + Resource getResource( + long id, boolean includeAttributes, boolean includePermissions, boolean includeTags); /** * @param resourceSearchParameters the object encapsulating search criteria such as pagination, diff --git a/src/core/services-api/src/main/java/it/geosolutions/geostore/services/TagService.java b/src/core/services-api/src/main/java/it/geosolutions/geostore/services/TagService.java new file mode 100644 index 00000000..aea0e063 --- /dev/null +++ b/src/core/services-api/src/main/java/it/geosolutions/geostore/services/TagService.java @@ -0,0 +1,80 @@ +/* + * ==================================================================== + * + * Copyright (C) 2025 GeoSolutions S.A.S. + * http://www.geo-solutions.it + * + * GPLv3 + Classpath exception + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. + * + * ==================================================================== + * + * This software consists of voluntary contributions made by developers + * of GeoSolutions. For more information on GeoSolutions, please see + * . + * + */ +package it.geosolutions.geostore.services; + +import it.geosolutions.geostore.core.model.Tag; +import it.geosolutions.geostore.services.exception.BadRequestServiceEx; +import it.geosolutions.geostore.services.exception.NotFoundServiceEx; +import java.util.List; + +public interface TagService { + + /** + * @param tag + * @return long + * @throws BadRequestServiceEx + */ + long insert(Tag tag) throws BadRequestServiceEx; + + /** + * @param page + * @param entries + * @param nameLike + * @return List + * @throws BadRequestServiceEx + */ + List getAll(Integer page, Integer entries, String nameLike) throws BadRequestServiceEx; + + /** + * @param id + * @return Tag + */ + Tag get(long id); + + /** + * @param id + * @param tag + * @return long + * @throws NotFoundServiceEx + * @throws BadRequestServiceEx + */ + long update(long id, Tag tag) throws BadRequestServiceEx, NotFoundServiceEx; + + /** + * @param id + * @return + */ + void delete(long id) throws NotFoundServiceEx; + + long count(String nameLike); + + void addToResource(long id, long resourceId) throws NotFoundServiceEx; + + void removeFromResource(long id, long resourceId) throws NotFoundServiceEx; +} diff --git a/src/core/services-api/src/main/java/it/geosolutions/geostore/services/dto/ResourceSearchParameters.java b/src/core/services-api/src/main/java/it/geosolutions/geostore/services/dto/ResourceSearchParameters.java index 066b64c0..142b5f49 100644 --- a/src/core/services-api/src/main/java/it/geosolutions/geostore/services/dto/ResourceSearchParameters.java +++ b/src/core/services-api/src/main/java/it/geosolutions/geostore/services/dto/ResourceSearchParameters.java @@ -12,6 +12,7 @@ public class ResourceSearchParameters { private final String nameLike; private final boolean includeAttributes; private final boolean includeData; + private final boolean includeTags; private final User authUser; private ResourceSearchParameters( @@ -23,6 +24,7 @@ private ResourceSearchParameters( String nameLike, boolean includeAttributes, boolean includeData, + boolean includeTags, User authUser) { this.filter = filter; this.page = page; @@ -32,6 +34,7 @@ private ResourceSearchParameters( this.nameLike = nameLike; this.includeAttributes = includeAttributes; this.includeData = includeData; + this.includeTags = includeTags; this.authUser = authUser; } @@ -67,6 +70,10 @@ public boolean isIncludeData() { return includeData; } + public boolean isIncludeTags() { + return includeTags; + } + public User getAuthUser() { return authUser; } @@ -84,6 +91,7 @@ public static class Builder { private String nameLike; private boolean includeAttributes; private boolean includeData; + private boolean includeTags; private User authUser; private Builder() {} @@ -128,6 +136,11 @@ public Builder includeData(boolean includeData) { return this; } + public Builder includeTags(boolean includeTags) { + this.includeTags = includeTags; + return this; + } + public Builder authUser(User authUser) { this.authUser = authUser; return this; @@ -143,6 +156,7 @@ public ResourceSearchParameters build() { nameLike, includeAttributes, includeData, + includeTags, authUser); } } diff --git a/src/core/services-api/src/main/java/it/geosolutions/geostore/services/dto/search/AndFilter.java b/src/core/services-api/src/main/java/it/geosolutions/geostore/services/dto/search/AndFilter.java index ef5cc29d..83fabff6 100644 --- a/src/core/services-api/src/main/java/it/geosolutions/geostore/services/dto/search/AndFilter.java +++ b/src/core/services-api/src/main/java/it/geosolutions/geostore/services/dto/search/AndFilter.java @@ -55,7 +55,8 @@ public AndFilter(SearchFilter f1, SearchFilter f2, SearchFilter... other) { @XmlElement(name = "AND", type = AndFilter.class), @XmlElement(name = "FIELD", type = FieldFilter.class), @XmlElement(name = "CATEGORY", type = CategoryFilter.class), - @XmlElement(name = "GROUP", type = GroupFilter.class) + @XmlElement(name = "GROUP", type = GroupFilter.class), + @XmlElement(name = "TAG", type = TagFilter.class) }) public List getFilters() { return filters; diff --git a/src/core/services-api/src/main/java/it/geosolutions/geostore/services/dto/search/AssociatedEntityFilter.java b/src/core/services-api/src/main/java/it/geosolutions/geostore/services/dto/search/AssociatedEntityFilter.java new file mode 100644 index 00000000..4cfb6170 --- /dev/null +++ b/src/core/services-api/src/main/java/it/geosolutions/geostore/services/dto/search/AssociatedEntityFilter.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2025 GeoSolutions S.A.S. + * http://www.geo-solutions.it + * + * GPLv3 + Classpath exception + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package it.geosolutions.geostore.services.dto.search; + +import it.geosolutions.geostore.services.exception.BadRequestServiceEx; +import it.geosolutions.geostore.services.exception.InternalErrorServiceEx; +import java.io.Serializable; +import java.util.Collections; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import javax.xml.bind.annotation.XmlElement; + +/** + * {@link SearchFilter} implementation that represent a filter by name on a {@link + * it.geosolutions.geostore.core.model.Resource}-associated entity. + */ +public abstract class AssociatedEntityFilter extends SearchFilter implements Serializable { + + private static final Pattern NAMES_PATTERN = Pattern.compile("\"([^\"]*)\"|([^,]+)"); + private static final List VALID_OPERATORS = + List.of( + SearchOperator.EQUAL_TO, + SearchOperator.LIKE, + SearchOperator.ILIKE, + SearchOperator.IN); + + private SearchOperator operator; + private List values; + + public AssociatedEntityFilter() {} + + public AssociatedEntityFilter(String names, SearchOperator operator) { + setOperator(operator); + setNames(names); + } + + /** + * Setter to map the deserialized filter names. + * + *

To search for multiple names, use a comma-separated list. If a name contains a comma, wrap + * it in double quotes (e.g. "name,with,commas"). + * + * @param names a comma-separated list of names to filter the resource with + */ + @XmlElement + public void setNames(String names) { + + if (names == null) { + throw new IllegalArgumentException("filter names should not be null"); + } + + if (operator == SearchOperator.IN) { + this.values = + NAMES_PATTERN + .matcher(names) + .results() + .map(r -> r.group(1) != null ? r.group(1) : r.group(2)) + .collect(Collectors.toList()); + } else { + this.values = Collections.singletonList(names); + } + } + + public SearchOperator getOperator() { + return operator; + } + + public void setOperator(SearchOperator operator) { + this.operator = operator; + checkOperator(); + } + + public List values() { + return values; + } + + public abstract String property(); + + @Override + public void accept(FilterVisitor visitor) throws BadRequestServiceEx, InternalErrorServiceEx { + checkOperator(); + visitor.visit(this); + } + + private void checkOperator() { + if (!VALID_OPERATORS.contains(operator)) + throw new IllegalArgumentException( + "Only EQUAL_TO, LIKE, ILIKE, or IN operators are acceptable"); + } + + @Override + public String toString() { + return getClass().getSimpleName() + + '[' + + (operator != null ? operator : "!op!") + + ' ' + + (values != null ? values : "[!names!]") + + ']'; + } +} diff --git a/src/core/services-api/src/main/java/it/geosolutions/geostore/services/dto/search/FilterVisitor.java b/src/core/services-api/src/main/java/it/geosolutions/geostore/services/dto/search/FilterVisitor.java index ebd7cf20..b099ab50 100644 --- a/src/core/services-api/src/main/java/it/geosolutions/geostore/services/dto/search/FilterVisitor.java +++ b/src/core/services-api/src/main/java/it/geosolutions/geostore/services/dto/search/FilterVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007 - 2011 GeoSolutions S.A.S. + * Copyright (C) 2007 - 2025 GeoSolutions S.A.S. * http://www.geo-solutions.it * * GPLv3 + Classpath exception @@ -35,7 +35,7 @@ public interface FilterVisitor { void visit(CategoryFilter filter) throws InternalErrorServiceEx; - void visit(GroupFilter filter) throws InternalErrorServiceEx; + void visit(AssociatedEntityFilter filter); void visit(FieldFilter filter) throws InternalErrorServiceEx; diff --git a/src/core/services-api/src/main/java/it/geosolutions/geostore/services/dto/search/GroupFilter.java b/src/core/services-api/src/main/java/it/geosolutions/geostore/services/dto/search/GroupFilter.java index 9e7ef0dd..148c9987 100644 --- a/src/core/services-api/src/main/java/it/geosolutions/geostore/services/dto/search/GroupFilter.java +++ b/src/core/services-api/src/main/java/it/geosolutions/geostore/services/dto/search/GroupFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007 - 2011 GeoSolutions S.A.S. + * Copyright (C) 2025 GeoSolutions S.A.S. * http://www.geo-solutions.it * * GPLv3 + Classpath exception @@ -19,80 +19,20 @@ */ package it.geosolutions.geostore.services.dto.search; -import it.geosolutions.geostore.services.exception.BadRequestServiceEx; -import it.geosolutions.geostore.services.exception.InternalErrorServiceEx; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; import javax.xml.bind.annotation.XmlRootElement; /** Filter by group name */ @XmlRootElement(name = "Group") -public class GroupFilter extends SearchFilter implements Serializable { +public class GroupFilter extends AssociatedEntityFilter { - private List names = new ArrayList<>(); - - private SearchOperator operator; - - /** */ public GroupFilter() {} - /** - * @param names - * @param operator - */ - public GroupFilter(List names, SearchOperator operator) { - this.names = names; - setOperator(operator); - } - - /** @return the names */ - public List getNames() { - return names; - } - - /** @param names the names to set */ - public void setNames(List names) { - this.names = names; - } - - /** @return the operator */ - public SearchOperator getOperator() { - return operator; - } - - /** @param operator the operator to set */ - public final void setOperator(SearchOperator operator) { - checkOperator(operator); - this.operator = operator; - } - - public static void checkOperator(SearchOperator operator) { - if (operator != SearchOperator.EQUAL_TO - && operator != SearchOperator.LIKE - && operator != SearchOperator.ILIKE - && operator != SearchOperator.IN) - throw new IllegalArgumentException( - "Only EQUAL_TO, LIKE, ILIKE, or IN operators are acceptable"); - } - - @Override - public void accept(FilterVisitor visitor) throws BadRequestServiceEx, InternalErrorServiceEx { - visitor.visit(this); + public GroupFilter(String names, SearchOperator operator) { + super(names, operator); } - /* - * (non-Javadoc) - * - * @see java.lang.Object#toString() - */ @Override - public String toString() { - return getClass().getSimpleName() - + '[' - + (operator != null ? operator : "!op!") - + ' ' - + (names != null ? names : "[!names!]") - + ']'; + public String property() { + return "security.group.groupName"; } } diff --git a/src/core/services-api/src/main/java/it/geosolutions/geostore/services/dto/search/OrFilter.java b/src/core/services-api/src/main/java/it/geosolutions/geostore/services/dto/search/OrFilter.java index 252c2d6d..9dea8ea2 100644 --- a/src/core/services-api/src/main/java/it/geosolutions/geostore/services/dto/search/OrFilter.java +++ b/src/core/services-api/src/main/java/it/geosolutions/geostore/services/dto/search/OrFilter.java @@ -55,7 +55,8 @@ public OrFilter(SearchFilter f1, SearchFilter f2, SearchFilter... other) { @XmlElement(name = "AND", type = AndFilter.class), @XmlElement(name = "FIELD", type = FieldFilter.class), @XmlElement(name = "CATEGORY", type = CategoryFilter.class), - @XmlElement(name = "GROUP", type = GroupFilter.class) + @XmlElement(name = "GROUP", type = GroupFilter.class), + @XmlElement(name = "TAG", type = TagFilter.class) }) public List getFilters() { return filters; diff --git a/src/core/services-api/src/main/java/it/geosolutions/geostore/services/dto/search/TagFilter.java b/src/core/services-api/src/main/java/it/geosolutions/geostore/services/dto/search/TagFilter.java new file mode 100644 index 00000000..0d85073c --- /dev/null +++ b/src/core/services-api/src/main/java/it/geosolutions/geostore/services/dto/search/TagFilter.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2025 GeoSolutions S.A.S. + * http://www.geo-solutions.it + * + * GPLv3 + Classpath exception + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package it.geosolutions.geostore.services.dto.search; + +import javax.xml.bind.annotation.XmlRootElement; + +/** Filter by tag name */ +@XmlRootElement(name = "Tag") +public class TagFilter extends AssociatedEntityFilter { + + public TagFilter() {} + + public TagFilter(String values, SearchOperator operator) { + super(values, operator); + } + + @Override + public String property() { + return "tags.name"; + } +} diff --git a/src/core/services-impl/src/main/java/it/geosolutions/geostore/services/ResourceServiceImpl.java b/src/core/services-impl/src/main/java/it/geosolutions/geostore/services/ResourceServiceImpl.java index 5ae0b5ff..161ec2f3 100644 --- a/src/core/services-impl/src/main/java/it/geosolutions/geostore/services/ResourceServiceImpl.java +++ b/src/core/services-impl/src/main/java/it/geosolutions/geostore/services/ResourceServiceImpl.java @@ -89,32 +89,26 @@ public class ResourceServiceImpl implements ResourceService { private ResourcePermissionService resourcePermissionService; - /** @param securityDAO the securityDAO to set */ public void setSecurityDAO(SecurityDAO securityDAO) { this.securityDAO = securityDAO; } - /** @param storedDataDAO the storedDataDAO to set */ public void setStoredDataDAO(StoredDataDAO storedDataDAO) { this.storedDataDAO = storedDataDAO; } - /** @param resourceDAO */ public void setResourceDAO(ResourceDAO resourceDAO) { this.resourceDAO = resourceDAO; } - /** @param attributeDAO */ public void setAttributeDAO(AttributeDAO attributeDAO) { this.attributeDAO = attributeDAO; } - /** @param categoryDAO the categoryDAO to set */ public void setCategoryDAO(CategoryDAO categoryDAO) { this.categoryDAO = categoryDAO; } - /** @param userGroupDAO the userGroupDAO to set */ public void setUserGroupDAO(UserGroupDAO userGroupDAO) { this.userGroupDAO = userGroupDAO; } @@ -359,25 +353,21 @@ private String suggestValidResourceName(String baseResourceName) { return validName; } - /* - * @param id - * - * @return the Resource or null if none was found with given id - * - * @see it.geosolutions.geostore.services.ResourceService#get(long) - */ @Override public Resource get(long id) { return resourceDAO.find(id); } @Override - public Resource getResource(long id, boolean includeAttributes, boolean includePermissions) { + public Resource getResource( + long id, boolean includeAttributes, boolean includePermissions, boolean includeTags) { Resource resource = resourceDAO.find(id); if (resource != null) { - resource = configResource(resource, includeAttributes, false, includePermissions); + resource = + configResource( + resource, includeAttributes, false, includePermissions, includeTags); } return resource; @@ -604,7 +594,8 @@ public List getResources(ResourceSearchParameters resourceSearchParame return this.configResourceList( searchResources(resourceSearchParameters), resourceSearchParameters.isIncludeAttributes(), - resourceSearchParameters.isIncludeData()); + resourceSearchParameters.isIncludeData(), + resourceSearchParameters.isIncludeTags()); } /** @@ -614,9 +605,12 @@ public List getResources(ResourceSearchParameters resourceSearchParame * @return List */ private List configResourceList( - List resources, boolean includeAttributes, boolean includeData) { + List resources, + boolean includeAttributes, + boolean includeData, + boolean includeTags) { return resources.stream() - .map(r -> configResource(r, includeAttributes, includeData, false)) + .map(r -> configResource(r, includeAttributes, includeData, false, includeTags)) .collect(Collectors.toList()); } @@ -624,7 +618,8 @@ private Resource configResource( Resource resource, boolean includeAttributes, boolean includeData, - boolean includePermissions) { + boolean includePermissions, + boolean includeTags) { Resource configuredResource = new Resource(); @@ -650,6 +645,10 @@ private Resource configResource( configuredResource.setSecurity(getSecurityRules(resource.getId())); } + if (includeTags) { + configuredResource.setTags(resource.getTags()); + } + return configuredResource; } @@ -818,6 +817,7 @@ public long getCountByFilterAndUser(SearchFilter filter, User user) throws BadRequestServiceEx, InternalErrorServiceEx { Search searchCriteria = SearchConverter.convert(filter); securityDAO.addAdvertisedSecurityConstraints(searchCriteria, user); + searchCriteria.setDistinct(true); return resourceDAO.count(searchCriteria); } diff --git a/src/core/services-impl/src/main/java/it/geosolutions/geostore/services/TagServiceImpl.java b/src/core/services-impl/src/main/java/it/geosolutions/geostore/services/TagServiceImpl.java new file mode 100644 index 00000000..cbb4fb71 --- /dev/null +++ b/src/core/services-impl/src/main/java/it/geosolutions/geostore/services/TagServiceImpl.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2025 GeoSolutions S.A.S. + * http://www.geo-solutions.it + * + * GPLv3 + Classpath exception + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package it.geosolutions.geostore.services; + +import com.googlecode.genericdao.search.Search; +import it.geosolutions.geostore.core.dao.ResourceDAO; +import it.geosolutions.geostore.core.dao.TagDAO; +import it.geosolutions.geostore.core.model.Resource; +import it.geosolutions.geostore.core.model.Tag; +import it.geosolutions.geostore.services.exception.BadRequestServiceEx; +import it.geosolutions.geostore.services.exception.NotFoundServiceEx; +import java.util.List; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.transaction.annotation.Transactional; + +public class TagServiceImpl implements TagService { + + private static final Logger LOGGER = LogManager.getLogger(TagServiceImpl.class); + + private TagDAO tagDAO; + private ResourceDAO resourceDAO; + + public void setTagDAO(TagDAO tagDAO) { + this.tagDAO = tagDAO; + } + + public void setResourceDAO(ResourceDAO resourceDAO) { + this.resourceDAO = resourceDAO; + } + + @Override + public long insert(Tag tag) throws BadRequestServiceEx { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Persisting Tag ... "); + } + + if (tag == null) { + throw new BadRequestServiceEx("Tag must be specified"); + } + + tagDAO.persist(tag); + + return tag.getId(); + } + + @Override + public List getAll(Integer page, Integer entries, String nameLike) + throws BadRequestServiceEx { + + if (page != null && entries == null || page == null && entries != null) { + throw new BadRequestServiceEx("Page and entries params should be declared together."); + } + + Search searchCriteria = new Search(Tag.class); + + searchCriteria.addSortAsc("name"); + + if (page != null) { + searchCriteria.setMaxResults(entries); + searchCriteria.setPage(page); + } + if (nameLike != null) { + searchCriteria.addFilterILike("name", nameLike); + } + + return tagDAO.search(searchCriteria); + } + + @Override + public Tag get(long id) { + return tagDAO.find(id); + } + + @Override + public long update(long id, Tag tag) throws BadRequestServiceEx, NotFoundServiceEx { + Tag original = get(id); + if (original == null) { + throw new NotFoundServiceEx("Tag not found"); + } + + tag.setId(id); + tag.setResources(original.getResources()); + + tagDAO.merge(tag); + + return id; + } + + @Override + public void delete(long id) throws NotFoundServiceEx { + if (get(id) == null || !tagDAO.removeById(id)) { + throw new NotFoundServiceEx("Tag not found"); + } + } + + @Override + public long count(String nameLike) { + + Search searchCriteria = new Search(Tag.class); + + if (nameLike != null) { + searchCriteria.addFilterILike("name", nameLike); + } + + return tagDAO.count(searchCriteria); + } + + @Override + @Transactional(value = "geostoreTransactionManager") + public void addToResource(long id, long resourceId) throws NotFoundServiceEx { + + Tag tag = get(id); + if (tag == null) { + throw new NotFoundServiceEx("Tag not found"); + } + + Resource resource = resourceDAO.find(resourceId); + if (resource == null) { + throw new NotFoundServiceEx("Resource not found"); + } + + tag.getResources().add(resource); + + tagDAO.persist(tag); + } + + @Override + @Transactional(value = "geostoreTransactionManager") + public void removeFromResource(long id, long resourceId) throws NotFoundServiceEx { + + Tag tag = get(id); + if (tag == null) { + throw new NotFoundServiceEx("Tag not found"); + } + + Resource resource = resourceDAO.find(resourceId); + if (resource == null) { + throw new NotFoundServiceEx("Resource not found"); + } + + tag.getResources().remove(resource); + + tagDAO.persist(tag); + } +} diff --git a/src/core/services-impl/src/main/java/it/geosolutions/geostore/util/SearchConverter.java b/src/core/services-impl/src/main/java/it/geosolutions/geostore/util/SearchConverter.java index c7c03308..04e6d56a 100644 --- a/src/core/services-impl/src/main/java/it/geosolutions/geostore/util/SearchConverter.java +++ b/src/core/services-impl/src/main/java/it/geosolutions/geostore/util/SearchConverter.java @@ -23,11 +23,11 @@ import com.googlecode.genericdao.search.Search; import it.geosolutions.geostore.core.model.Resource; import it.geosolutions.geostore.services.dto.search.AndFilter; +import it.geosolutions.geostore.services.dto.search.AssociatedEntityFilter; import it.geosolutions.geostore.services.dto.search.AttributeFilter; import it.geosolutions.geostore.services.dto.search.CategoryFilter; import it.geosolutions.geostore.services.dto.search.FieldFilter; import it.geosolutions.geostore.services.dto.search.FilterVisitor; -import it.geosolutions.geostore.services.dto.search.GroupFilter; import it.geosolutions.geostore.services.dto.search.NotFilter; import it.geosolutions.geostore.services.dto.search.OrFilter; import it.geosolutions.geostore.services.dto.search.SearchFilter; @@ -254,31 +254,25 @@ public void visit(CategoryFilter filter) { trgFilter = f; } - /** This is a leaf filter. */ @Override - public void visit(GroupFilter filter) { - GroupFilter.checkOperator(filter.getOperator()); + public void visit(AssociatedEntityFilter filter) { - Integer op = ops_rest_trg.get(filter.getOperator()); + SearchOperator searchOperator = filter.getOperator(); + List values = filter.values(); - if (op == null) { - throw new IllegalStateException("Unknown op " + filter.getOperator()); + Integer operator = ops_rest_trg.get(searchOperator); + if (operator == null) { + throw new IllegalStateException("Unknown op " + searchOperator); } Filter f = new Filter(); - f.setOperator(op); - f.setProperty("security.group.groupName"); - - List names = filter.getNames(); - - if (SearchOperator.IN != filter.getOperator() && names.size() != 1) { - throw new IllegalStateException("Erroneous search op " + filter.getOperator()); - } + f.setOperator(operator); + f.setProperty(filter.property()); - if (SearchOperator.IN == filter.getOperator()) { - f.setValue(names); + if (SearchOperator.IN == searchOperator) { + f.setValue(values); } else { - f.setValue(names.get(0)); + f.setValue(values.get(0)); } trgFilter = f; diff --git a/src/core/services-impl/src/main/resources/applicationContext.xml b/src/core/services-impl/src/main/resources/applicationContext.xml index 8d76e350..66430b29 100644 --- a/src/core/services-impl/src/main/resources/applicationContext.xml +++ b/src/core/services-impl/src/main/resources/applicationContext.xml @@ -1,28 +1,23 @@ - + - + - + - + - + - + + + diff --git a/src/core/services-impl/src/test/java/it/geosolutions/geostore/services/ServiceTestBase.java b/src/core/services-impl/src/test/java/it/geosolutions/geostore/services/ServiceTestBase.java index dbd2df3e..e4ef8009 100644 --- a/src/core/services-impl/src/test/java/it/geosolutions/geostore/services/ServiceTestBase.java +++ b/src/core/services-impl/src/test/java/it/geosolutions/geostore/services/ServiceTestBase.java @@ -20,10 +20,12 @@ package it.geosolutions.geostore.services; import it.geosolutions.geostore.core.dao.ResourceDAO; +import it.geosolutions.geostore.core.dao.TagDAO; import it.geosolutions.geostore.core.model.Category; import it.geosolutions.geostore.core.model.Resource; import it.geosolutions.geostore.core.model.SecurityRule; import it.geosolutions.geostore.core.model.StoredData; +import it.geosolutions.geostore.core.model.Tag; import it.geosolutions.geostore.core.model.User; import it.geosolutions.geostore.core.model.UserAttribute; import it.geosolutions.geostore.core.model.UserGroup; @@ -58,8 +60,12 @@ public class ServiceTestBase extends TestCase { protected static UserGroupService userGroupService; + protected static TagService tagService; + protected static ResourceDAO resourceDAO; + protected static TagDAO tagDAO; + protected static ClassPathXmlApplicationContext ctx = null; protected final Logger LOGGER = LogManager.getLogger(getClass()); @@ -78,7 +84,9 @@ public ServiceTestBase() { categoryService = (CategoryService) ctx.getBean("categoryService"); userService = (UserService) ctx.getBean("userService"); userGroupService = (UserGroupService) ctx.getBean("userGroupService"); + tagService = (TagService) ctx.getBean("tagService"); resourceDAO = (ResourceDAO) ctx.getBean("resourceDAO"); + tagDAO = (TagDAO) ctx.getBean("tagDAO"); } } } @@ -100,6 +108,7 @@ public void testCheckServices() { assertNotNull(categoryService); assertNotNull(userService); assertNotNull(userGroupService); + assertNotNull(tagService); } /** @@ -109,6 +118,7 @@ public void testCheckServices() { protected void removeAll() throws NotFoundServiceEx, BadRequestServiceEx, InternalErrorServiceEx { LOGGER.info("***** removeAll()"); + removeAllTag(); removeAllResource(); removeAllStoredData(); removeAllCategory(); @@ -116,6 +126,20 @@ protected void removeAll() removeAllUserGroup(); } + private void removeAllTag() throws BadRequestServiceEx { + tagService + .getAll(null, null, null) + .forEach( + item -> { + LOGGER.info("Removing tag: {}", item.getName()); + try { + tagService.delete(item.getId()); + } catch (NotFoundServiceEx e) { + throw new RuntimeException(e); + } + }); + } + /** * @throws BadRequestServiceEx * @throws NotFoundServiceEx @@ -401,6 +425,10 @@ protected long createGroup(String name) throws Exception { return userGroupService.insert(group); } + protected long createTag(String name, String color, String description) throws Exception { + return tagService.insert(new Tag(name, color, description)); + } + protected User buildFakeAdminUser() { User user = new User(); user.setRole(Role.ADMIN); @@ -410,6 +438,7 @@ protected User buildFakeAdminUser() { // SecurityRuleBuilder class protected class SecurityRuleBuilder { + private SecurityRule rule; public SecurityRuleBuilder() { diff --git a/src/core/services-impl/src/test/java/it/geosolutions/geostore/services/TagServiceImplTest.java b/src/core/services-impl/src/test/java/it/geosolutions/geostore/services/TagServiceImplTest.java new file mode 100644 index 00000000..28af302d --- /dev/null +++ b/src/core/services-impl/src/test/java/it/geosolutions/geostore/services/TagServiceImplTest.java @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2025 GeoSolutions S.A.S. + * http://www.geo-solutions.it + * + * GPLv3 + Classpath exception + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package it.geosolutions.geostore.services; + +import static org.junit.Assert.assertThrows; + +import it.geosolutions.geostore.core.model.Resource; +import it.geosolutions.geostore.core.model.Tag; +import it.geosolutions.geostore.services.exception.BadRequestServiceEx; +import it.geosolutions.geostore.services.exception.NotFoundServiceEx; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +public class TagServiceImplTest extends ServiceTestBase { + + public TagServiceImplTest() {} + + public void testInsert() throws Exception { + + Tag tagA = new Tag("tag-A", "#4561aa", "dusky"); + Tag tagB = new Tag("tag-B", "black", null); + + tagService.insert(tagA); + tagService.insert(tagB); + + List foundTags = tagDAO.findAll(); + assertEquals(2, foundTags.size()); + List foundTagsIds = foundTags.stream().map(Tag::getId).collect(Collectors.toList()); + assertTrue(foundTagsIds.stream().noneMatch(Objects::isNull)); + } + + public void testInsertNull() throws Exception { + assertThrows(BadRequestServiceEx.class, () -> tagService.insert(null)); + } + + public void testGetAll() throws Exception { + + final Tag tag_a = new Tag("tag-A", "#4561aa", "dusky"); + final Tag tag_b = new Tag("tag-B", "black", null); + + tagDAO.persist(tag_a, tag_b); + + List foundTags = tagService.getAll(0, 100, null); + assertEquals(List.of(tag_a, tag_b), foundTags); + } + + public void testGetAllPaginated() throws Exception { + + final Tag tag_a = new Tag("tag-A", "#4561aa", "dusky"); + final Tag tag_b = new Tag("tag-B", "black", null); + final Tag tag_c = new Tag("tag-C", "navy", "kind of blue"); + + tagDAO.persist(tag_a, tag_b, tag_c); + + List firstPageTags = tagService.getAll(0, 2, null); + assertEquals(List.of(tag_a, tag_b), firstPageTags); + List secondPageTags = tagService.getAll(1, 2, null); + assertEquals(List.of(tag_c), secondPageTags); + } + + public void testGetAllFiltered() throws Exception { + + final Tag tag_a = new Tag("tag-A", "#4561aa", "dusky"); + final Tag tag_b = new Tag("tag-B", "black", null); + final Tag tag_c = new Tag("C", "navy", "kind of blue"); + + tagDAO.persist(tag_a, tag_b, tag_c); + + List foundTags = tagService.getAll(0, 100, "tag%"); + assertEquals(List.of(tag_a, tag_b), foundTags); + } + + public void testGet() throws Exception { + + final Tag tag_a = new Tag("tag-A", "#4561aa", "dusky"); + final Tag tag_b = new Tag("tag-B", "black", null); + + tagDAO.persist(tag_a, tag_b); + + Tag foundTag = tagService.get(tag_a.getId()); + assertEquals(tag_a, foundTag); + } + + public void testUpdate() throws Exception { + + final Tag expected_tag = new Tag("updated name", "black", null); + + Tag tag = new Tag("tag", "#4561aa", "dusky"); + tagDAO.persist(tag); + + tagService.update(tag.getId(), expected_tag); + + Tag updatedTag = tagDAO.find(tag.getId()); + assertEquals(expected_tag, updatedTag); + } + + public void testUpdateWithResource() throws Exception { + + Tag update_tag = new Tag("updated name", "black", null); + + Tag tag = new Tag("tag", "#4561aa", "dusky"); + + long resourceId = createResource("resource", "description", "category"); + Resource resource = resourceService.get(resourceId); + + tag.setResources(Collections.singleton(resource)); + tagDAO.persist(tag); + + tagService.update(tag.getId(), update_tag); + + Tag updatedTag = tagDAO.find(tag.getId()); + + /* check if resource is still tagged */ + Resource updatedTagResource = resourceService.get(resourceId); + Set updatedTagResourceTags = updatedTagResource.getTags(); + assertEquals(1, updatedTagResourceTags.size()); + Tag resourceTag = updatedTagResourceTags.stream().findFirst().orElseThrow(); + assertEquals(updatedTag, resourceTag); + } + + public void testUpdateNotFoundTag() throws Exception { + assertThrows(NotFoundServiceEx.class, () -> tagService.update(0L, new Tag())); + } + + public void testDelete() throws Exception { + + Tag tag = new Tag("tag", "#4561aa", "dusky"); + tagDAO.persist(tag); + + tagService.delete(tag.getId()); + + Tag foundTag = tagDAO.find(tag.getId()); + assertNull(foundTag); + } + + public void testDeleteNotFoundTag() throws Exception { + assertThrows(NotFoundServiceEx.class, () -> tagService.delete(0L)); + } + + public void testAddToResource() throws Exception { + + final Tag tag = new Tag("tag", "#4561aa", "dusky"); + + long resourceId = createResource("resource", "description", "category"); + + tagDAO.persist(tag); + + tagService.addToResource(tag.getId(), resourceId); + + Resource resource = resourceDAO.find(resourceId); + Set resourceTags = resource.getTags(); + assertEquals(1, resourceTags.size()); + Tag resourceTag = resourceTags.stream().findFirst().orElseThrow(); + assertEquals(tag, resourceTag); + } + + public void testAddToResourceNotFoundTag() throws Exception { + long resourceId = createResource("resource", "description", "category"); + assertThrows(NotFoundServiceEx.class, () -> tagService.addToResource(0L, resourceId)); + } + + public void testAddToResourceNotFoundResource() throws Exception { + Tag tag = new Tag("tag", "#4561aa", "dusky"); + tagDAO.persist(tag); + assertThrows(NotFoundServiceEx.class, () -> tagService.addToResource(tag.getId(), 0L)); + } + + public void testRemoveFromResource() throws Exception { + + Tag tag = new Tag("tag", "#4561aa", "dusky"); + long resourceId = createResource("resource", "description", "category"); + + tagDAO.persist(tag); + + Resource resource = resourceDAO.find(resourceId); + resource.setTags(Collections.singleton(tag)); + resourceService.update(resource); + + tagService.removeFromResource(tag.getId(), resourceId); + + resource = resourceDAO.find(resourceId); + Set resourceTags = resource.getTags(); + assertTrue(resourceTags.isEmpty()); + } + + public void testRemoveFromResourceNotFoundTag() throws Exception { + long resourceId = createResource("resource", "description", "category"); + assertThrows(NotFoundServiceEx.class, () -> tagService.removeFromResource(0L, resourceId)); + } + + public void testRemoveFromResourceNotFoundResource() throws Exception { + Tag tag = new Tag("tag", "#4561aa", "dusky"); + tagDAO.persist(tag); + assertThrows(NotFoundServiceEx.class, () -> tagService.removeFromResource(tag.getId(), 0L)); + } +} diff --git a/src/modules/rest/api/src/main/java/it/geosolutions/geostore/services/rest/RESTTagService.java b/src/modules/rest/api/src/main/java/it/geosolutions/geostore/services/rest/RESTTagService.java new file mode 100644 index 00000000..44ba134c --- /dev/null +++ b/src/modules/rest/api/src/main/java/it/geosolutions/geostore/services/rest/RESTTagService.java @@ -0,0 +1,148 @@ +/* ==================================================================== + * + * Copyright (C) 2007 - 2012 GeoSolutions S.A.S. + * http://www.geo-solutions.it + * + * GPLv3 + Classpath exception + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. + * + * ==================================================================== + * + * This software consists of voluntary contributions made by developers + * of GeoSolutions. For more information on GeoSolutions, please see + * . + * + */ +package it.geosolutions.geostore.services.rest; + +import it.geosolutions.geostore.core.model.Tag; +import it.geosolutions.geostore.services.exception.BadRequestServiceEx; +import it.geosolutions.geostore.services.rest.exception.BadRequestWebEx; +import it.geosolutions.geostore.services.rest.exception.NotFoundWebEx; +import it.geosolutions.geostore.services.rest.model.TagList; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.SecurityContext; +import org.apache.cxf.jaxrs.ext.multipart.Multipart; +import org.springframework.security.access.annotation.Secured; + +/** + * REST service mapped under the /resources path. For example, to call the "get all" + * operation, use the endpoint: GET /rest/resources/tag. + */ +@Path("tag") +public interface RESTTagService { + + /** + * @param tag + * @return long + * @throws BadRequestServiceEx + */ + @POST + @Consumes({MediaType.APPLICATION_XML, MediaType.TEXT_XML}) + @Produces({MediaType.TEXT_PLAIN}) + @Secured({"ROLE_ADMIN"}) + long insert(@Context SecurityContext sc, @Multipart("tag") Tag tag) throws BadRequestServiceEx; + + /** + * @param sc the security context + * @param page the requested page number + * @param entries max entries for page + * @param nameLike a sub-string to search in tag name with ILIKE operator + * @return Tag + * @throws BadRequestWebEx + */ + @GET + @Produces({MediaType.TEXT_XML, MediaType.APPLICATION_JSON}) + @Secured({"ROLE_ADMIN", "ROLE_USER", "ROLE_ANONYMOUS"}) + TagList getAll( + @Context SecurityContext sc, + @QueryParam("page") Integer page, + @QueryParam("entries") Integer entries, + @QueryParam("nameLike") String nameLike) + throws BadRequestWebEx; + + /** + * @param id + * @return Tag + * @throws NotFoundWebEx + */ + @GET + @Path("{id}") + @Produces({MediaType.TEXT_XML, MediaType.APPLICATION_JSON}) + @Secured({"ROLE_ADMIN", "ROLE_USER"}) + Tag get(@Context SecurityContext sc, @PathParam("id") long id) throws NotFoundWebEx; + + /** + * @param id + * @param tag + * @return long + * @throws NotFoundWebEx + * @throws BadRequestWebEx + */ + @PUT + @Path("{id}") + @Consumes({MediaType.APPLICATION_XML, MediaType.TEXT_XML}) + @Produces({MediaType.TEXT_PLAIN}) + @Secured({"ROLE_ADMIN"}) + long update(@Context SecurityContext sc, @PathParam("id") long id, @Multipart("tag") Tag tag) + throws NotFoundWebEx, BadRequestWebEx; + + /** + * @param id + * @throws NotFoundWebEx + */ + @DELETE + @Path("{id}") + @Secured({"ROLE_ADMIN"}) + void delete(@Context SecurityContext sc, @PathParam("id") long id) throws NotFoundWebEx; + + /** + * @param id tag identifier + * @param resourceId resource identifier + * @throws NotFoundWebEx + */ + @POST + @Path("/{id}/resource/{resourceId}") + @Secured({"ROLE_ADMIN", "ROLE_USER"}) + void addToResource( + @Context SecurityContext sc, + @PathParam("id") long id, + @PathParam("resourceId") long resourceId) + throws NotFoundWebEx; + + /** + * @param id tag identifier + * @param resourceId resource identifier + * @throws NotFoundWebEx + */ + @DELETE + @Path("/{id}/resource/{resourceId}") + @Secured({"ROLE_ADMIN", "ROLE_USER"}) + void removeFromResource( + @Context SecurityContext sc, + @PathParam("id") long id, + @PathParam("resourceId") long resourceId) + throws NotFoundWebEx; +} diff --git a/src/modules/rest/api/src/main/java/it/geosolutions/geostore/services/rest/model/TagList.java b/src/modules/rest/api/src/main/java/it/geosolutions/geostore/services/rest/model/TagList.java new file mode 100644 index 00000000..7eb778e6 --- /dev/null +++ b/src/modules/rest/api/src/main/java/it/geosolutions/geostore/services/rest/model/TagList.java @@ -0,0 +1,58 @@ +/* ==================================================================== + * + * Copyright (C) 2025 GeoSolutions S.A.S. + * http://www.geo-solutions.it + * + * GPLv3 + Classpath exception + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. + * + * ==================================================================== + * + * This software consists of voluntary contributions made by developers + * of GeoSolutions. For more information on GeoSolutions, please see + * . + * + */ + +package it.geosolutions.geostore.services.rest.model; + +import it.geosolutions.geostore.core.model.Tag; +import java.util.Collection; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement(name = "TagList") +public class TagList { + + private Collection list; + private Long count; + + public TagList() {} + + public TagList(Collection list, Long count) { + this.list = list; + this.count = count; + } + + @XmlElement(name = "Tag") + public Collection getList() { + return list; + } + + @XmlElement(name = "Count") + public Long getCount() { + return count; + } +} diff --git a/src/modules/rest/api/src/main/java/it/geosolutions/geostore/services/rest/utils/GeoStoreJAXBContext.java b/src/modules/rest/api/src/main/java/it/geosolutions/geostore/services/rest/utils/GeoStoreJAXBContext.java index 0481ccb4..fe0dd210 100644 --- a/src/modules/rest/api/src/main/java/it/geosolutions/geostore/services/rest/utils/GeoStoreJAXBContext.java +++ b/src/modules/rest/api/src/main/java/it/geosolutions/geostore/services/rest/utils/GeoStoreJAXBContext.java @@ -126,6 +126,7 @@ private static List> getAPIclasses() { it.geosolutions.geostore.services.dto.search.CategoryFilter.class, it.geosolutions.geostore.services.dto.search.FieldFilter.class, it.geosolutions.geostore.services.dto.search.GroupFilter.class, + it.geosolutions.geostore.services.dto.search.TagFilter.class, it.geosolutions.geostore.services.dto.search.NotFilter.class, it.geosolutions.geostore.services.dto.search.OrFilter.class, it.geosolutions.geostore.services.dto.search.SearchFilter.class, diff --git a/src/modules/rest/client/src/test/java/it/geosolutions/geostore/services/rest/GeoStoreClientTest.java b/src/modules/rest/client/src/test/java/it/geosolutions/geostore/services/rest/GeoStoreClientTest.java index 830aada9..b45300e8 100644 --- a/src/modules/rest/client/src/test/java/it/geosolutions/geostore/services/rest/GeoStoreClientTest.java +++ b/src/modules/rest/client/src/test/java/it/geosolutions/geostore/services/rest/GeoStoreClientTest.java @@ -271,6 +271,7 @@ public void testUpdateResource() { assertEquals("value4", attMap.get("string4")); assertEquals("USER1Updated", loaded.getCreator()); + /* TOFIX: this assertion fails */ assertEquals("USER2Updated", loaded.getEditor()); } diff --git a/src/modules/rest/extjs/src/main/java/it/geosolutions/geostore/services/model/ExtResource.java b/src/modules/rest/extjs/src/main/java/it/geosolutions/geostore/services/model/ExtResource.java index 925c7fbf..fc469ffb 100644 --- a/src/modules/rest/extjs/src/main/java/it/geosolutions/geostore/services/model/ExtResource.java +++ b/src/modules/rest/extjs/src/main/java/it/geosolutions/geostore/services/model/ExtResource.java @@ -32,6 +32,7 @@ private ExtResource(Builder builder) { this.setData(builder.resource.getData()); this.setCategory(builder.resource.getCategory()); this.setSecurity(builder.resource.getSecurity()); + this.setTags(builder.resource.getTags()); this.canEdit = builder.canEdit; this.canDelete = builder.canDelete; diff --git a/src/modules/rest/extjs/src/main/java/it/geosolutions/geostore/services/model/ExtResourceList.java b/src/modules/rest/extjs/src/main/java/it/geosolutions/geostore/services/model/ExtResourceList.java index fdad2ec6..68d94fc8 100644 --- a/src/modules/rest/extjs/src/main/java/it/geosolutions/geostore/services/model/ExtResourceList.java +++ b/src/modules/rest/extjs/src/main/java/it/geosolutions/geostore/services/model/ExtResourceList.java @@ -47,30 +47,25 @@ public class ExtResourceList { public ExtResourceList() {} - /** @param list */ public ExtResourceList(long count, List list) { this.count = count; this.list = list; } - /** @return the count */ @XmlElement(name = "ResourceCount") public long getCount() { return count; } - /** @param count the count to set */ public void setCount(long count) { this.count = count; } - /** @return List */ @XmlElement(name = "Resource") public List getList() { return list; } - /** @param list */ public void setList(List list) { this.list = list; } diff --git a/src/modules/rest/extjs/src/main/java/it/geosolutions/geostore/services/model/ExtShortResource.java b/src/modules/rest/extjs/src/main/java/it/geosolutions/geostore/services/model/ExtShortResource.java index 9ed9cb7c..1262b00a 100644 --- a/src/modules/rest/extjs/src/main/java/it/geosolutions/geostore/services/model/ExtShortResource.java +++ b/src/modules/rest/extjs/src/main/java/it/geosolutions/geostore/services/model/ExtShortResource.java @@ -3,6 +3,7 @@ import it.geosolutions.geostore.services.dto.ShortResource; import it.geosolutions.geostore.services.rest.model.SecurityRuleList; import it.geosolutions.geostore.services.rest.model.ShortAttributeList; +import it.geosolutions.geostore.services.rest.model.TagList; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; @@ -15,6 +16,7 @@ public class ExtShortResource extends ShortResource { @XmlElement private ShortAttributeList attributeList; @XmlElement private SecurityRuleList securityRuleList; + @XmlElement private TagList tagList; public ExtShortResource() {} @@ -32,6 +34,7 @@ private ExtShortResource(Builder builder) { this.attributeList = builder.attributeList; this.securityRuleList = builder.securityRuleList; + this.tagList = builder.tagList; } public ShortAttributeList getAttributeList() { @@ -42,6 +45,10 @@ public SecurityRuleList getSecurityRuleList() { return securityRuleList; } + public TagList getTagList() { + return tagList; + } + public static Builder builder(ShortResource resource) { return new Builder(resource); } @@ -50,6 +57,7 @@ public static class Builder { private final ShortResource shortResource; public ShortAttributeList attributeList; public SecurityRuleList securityRuleList; + public TagList tagList; private Builder(ShortResource shortResource) { this.shortResource = shortResource; @@ -65,6 +73,11 @@ public Builder withSecurityRules(SecurityRuleList securityRuleList) { return this; } + public Builder withTagList(TagList tagList) { + this.tagList = tagList; + return this; + } + public ExtShortResource build() { return new ExtShortResource(this); } diff --git a/src/modules/rest/extjs/src/main/java/it/geosolutions/geostore/services/rest/RESTExtJsService.java b/src/modules/rest/extjs/src/main/java/it/geosolutions/geostore/services/rest/RESTExtJsService.java index 3ad4783c..05ab5f75 100644 --- a/src/modules/rest/extjs/src/main/java/it/geosolutions/geostore/services/rest/RESTExtJsService.java +++ b/src/modules/rest/extjs/src/main/java/it/geosolutions/geostore/services/rest/RESTExtJsService.java @@ -126,9 +126,10 @@ String getResourcesByCategory( * @param start the n-th group shown as first in results * @param limit max entries per page * @param sort the sorting parameters for the results (includes sortBy and - * sortOrder) + * sortOrder) * @param includeAttributes whether to include attributes in the returned results * @param includeData whether to include data in the returned results + * @param includeTags whether to include tags in the returned results * @param filter the multipart filter object to apply for resource filtering * @return * @throws BadRequestWebEx @@ -147,6 +148,7 @@ ExtResourceList getExtResourcesList( @BeanParam Sort sort, @QueryParam("includeAttributes") @DefaultValue("false") boolean includeAttributes, @QueryParam("includeData") @DefaultValue("false") boolean includeData, + @QueryParam("includeTags") @DefaultValue("false") boolean includeTags, @Multipart("filter") SearchFilter filter) throws BadRequestWebEx, InternalErrorWebEx; @@ -192,6 +194,7 @@ ExtGroupList getGroupsList( * @param id the id of the resource to fetch * @param includeAttributes whether to include attributes in the returned resource * @param includePermissions whether to include permissions in the returned resource + * @param includeTags whether to include tags in the returned resource * @return the resource * @throws ForbiddenErrorWebEx if the resource is protected * @throws NotFoundWebEx if the resource is not found @@ -204,5 +207,6 @@ ExtShortResource getExtResource( @Context SecurityContext sc, @PathParam("id") long id, @QueryParam("includeAttributes") @DefaultValue("false") boolean includeAttributes, - @QueryParam("includePermissions") @DefaultValue("false") boolean includePermissions); + @QueryParam("includePermissions") @DefaultValue("false") boolean includePermissions, + @QueryParam("includeTags") @DefaultValue("false") boolean includeTags); } diff --git a/src/modules/rest/extjs/src/main/java/it/geosolutions/geostore/services/rest/impl/RESTExtJsServiceImpl.java b/src/modules/rest/extjs/src/main/java/it/geosolutions/geostore/services/rest/impl/RESTExtJsServiceImpl.java index 72986fde..c077f081 100644 --- a/src/modules/rest/extjs/src/main/java/it/geosolutions/geostore/services/rest/impl/RESTExtJsServiceImpl.java +++ b/src/modules/rest/extjs/src/main/java/it/geosolutions/geostore/services/rest/impl/RESTExtJsServiceImpl.java @@ -29,6 +29,7 @@ import it.geosolutions.geostore.core.model.Attribute; import it.geosolutions.geostore.core.model.Resource; +import it.geosolutions.geostore.core.model.Tag; import it.geosolutions.geostore.core.model.User; import it.geosolutions.geostore.core.model.UserGroup; import it.geosolutions.geostore.services.ResourcePermissionService; @@ -59,11 +60,13 @@ import it.geosolutions.geostore.services.rest.model.SecurityRuleList; import it.geosolutions.geostore.services.rest.model.ShortAttributeList; import it.geosolutions.geostore.services.rest.model.Sort; +import it.geosolutions.geostore.services.rest.model.TagList; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import javax.ws.rs.core.SecurityContext; import net.sf.json.JSON; @@ -140,13 +143,13 @@ public String getAllResources(SecurityContext sc, String nameLike, Integer start int page = start == 0 ? start : start / limit; try { - nameLike = nameLike.replaceAll("[*]", "%"); + String sqlNameLike = convertNameLikeToSqlSyntax(nameLike); // TODO: implement includeAttributes and includeData List resources = resourceService.getList( ResourceSearchParameters.builder() - .nameLike(nameLike) + .nameLike(sqlNameLike) .page(page) .entries(limit) .authUser(authUser) @@ -154,7 +157,7 @@ public String getAllResources(SecurityContext sc, String nameLike, Integer start long count = 0; if (resources != null && !resources.isEmpty()) { - count = resourceService.getCountByFilterAndUser(nameLike, authUser); + count = resourceService.getCountByFilterAndUser(sqlNameLike, authUser); } JSONObject result = makeJSONResult(true, count, resources, authUser); @@ -268,12 +271,13 @@ public String getResourcesByCategory( try { SearchFilter filter = new CategoryFilter(categoryName, SearchOperator.EQUAL_TO); if (resourceNameLike != null) { - resourceNameLike = resourceNameLike.replaceAll("[*]", "%"); filter = new AndFilter( filter, new FieldFilter( - BaseField.NAME, resourceNameLike, SearchOperator.ILIKE)); + BaseField.NAME, + convertNameLikeToSqlSyntax(resourceNameLike), + SearchOperator.ILIKE)); } boolean shouldIncludeAttributes = @@ -326,6 +330,7 @@ public ExtResourceList getExtResourcesList( Sort sort, boolean includeAttributes, boolean includeData, + boolean includeTags, SearchFilter filter) throws BadRequestWebEx { @@ -335,12 +340,12 @@ public ExtResourceList getExtResourcesList( if (LOGGER.isDebugEnabled()) { LOGGER.debug( - "getResourcesList(start=" - + start - + ", limit=" - + limit - + ", includeAttributes=" - + includeAttributes); + "getResourcesList(start={}, limit={}, includeAttributes={}, includeData={}, includeTags={}", + start, + limit, + includeAttributes, + includeData, + includeTags); } User authUser = null; @@ -369,6 +374,7 @@ public ExtResourceList getExtResourcesList( .sortOrder(sort.getSortOrder()) .includeAttributes(includeAttributes) .includeData(includeData) + .includeTags(includeTags) .authUser(authUser) .build()); @@ -475,12 +481,12 @@ public ExtUserList getUsersList( } try { - nameLike = nameLike.replaceAll("[*]", "%"); - List users = userService.getAll(page, limit, nameLike, includeAttributes); + String sqlNameLike = convertNameLikeToSqlSyntax(nameLike); + List users = userService.getAll(page, limit, sqlNameLike, includeAttributes); long count = 0; if (users != null && !users.isEmpty()) { - count = userService.getCount(nameLike); + count = userService.getCount(sqlNameLike); } return new ExtUserList(count, users); @@ -522,15 +528,13 @@ public ExtGroupList getGroupsList( } try { - if (nameLike != null) { - nameLike = nameLike.replaceAll("[*]", "%"); - } + String sqlNameLike = convertNameLikeToSqlSyntax(nameLike); List groups = - groupService.getAllAllowed(authUser, page, limit, nameLike, all); + groupService.getAllAllowed(authUser, page, limit, sqlNameLike, all); long count = 0; if (groups != null && !groups.isEmpty()) { - count = groupService.getCount(authUser, nameLike, all); + count = groupService.getCount(authUser, sqlNameLike, all); } return new ExtGroupList(count, groups); @@ -662,9 +666,14 @@ private JSONObject makeGeneralizedJSONResult( @Override public ExtShortResource getExtResource( - SecurityContext sc, long id, boolean includeAttributes, boolean includePermissions) { + SecurityContext sc, + long id, + boolean includeAttributes, + boolean includePermissions, + boolean includeTags) { - Resource resource = resourceService.getResource(id, includeAttributes, includePermissions); + Resource resource = + resourceService.getResource(id, includeAttributes, includePermissions, includeTags); if (resource == null) { throw new NotFoundWebEx("Resource not found"); @@ -686,6 +695,7 @@ public ExtShortResource getExtResource( return ExtShortResource.builder(shortResource) .withAttributes(createShortAttributeList(resource.getAttribute())) .withSecurityRules(new SecurityRuleList(resource.getSecurity())) + .withTagList(createTagList(resource.getTags())) .build(); } @@ -697,6 +707,13 @@ private ShortAttributeList createShortAttributeList(List attributes) attributes.stream().map(ShortAttribute::new).collect(Collectors.toList())); } + private TagList createTagList(Set tags) { + if (tags == null) { + return new TagList(); + } + return new TagList(tags, (long) tags.size()); + } + /** * Encapsulates resource/short resource and credentials to perform operations with resources * diff --git a/src/modules/rest/extjs/src/test/java/it/geosolutions/geostore/services/rest/impl/RESTExtJsServiceImplTest.java b/src/modules/rest/extjs/src/test/java/it/geosolutions/geostore/services/rest/impl/RESTExtJsServiceImplTest.java index b9bb1b23..e080c165 100644 --- a/src/modules/rest/extjs/src/test/java/it/geosolutions/geostore/services/rest/impl/RESTExtJsServiceImplTest.java +++ b/src/modules/rest/extjs/src/test/java/it/geosolutions/geostore/services/rest/impl/RESTExtJsServiceImplTest.java @@ -28,6 +28,7 @@ import it.geosolutions.geostore.core.model.Category; import it.geosolutions.geostore.core.model.Resource; import it.geosolutions.geostore.core.model.SecurityRule; +import it.geosolutions.geostore.core.model.Tag; import it.geosolutions.geostore.core.model.UserGroup; import it.geosolutions.geostore.core.model.enums.DataType; import it.geosolutions.geostore.core.model.enums.Role; @@ -39,6 +40,7 @@ import it.geosolutions.geostore.services.dto.search.FieldFilter; import it.geosolutions.geostore.services.dto.search.GroupFilter; import it.geosolutions.geostore.services.dto.search.SearchOperator; +import it.geosolutions.geostore.services.dto.search.TagFilter; import it.geosolutions.geostore.services.model.ExtGroupList; import it.geosolutions.geostore.services.model.ExtResource; import it.geosolutions.geostore.services.model.ExtResourceList; @@ -50,6 +52,7 @@ import it.geosolutions.geostore.services.rest.model.Sort; import java.text.SimpleDateFormat; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashSet; @@ -294,7 +297,7 @@ public void testGetAllResources_editorUpdate() throws Exception { assertEquals(1, result.total); assertEquals(1, result.returnedCount); - ShortResource resource = restExtJsService.getExtResource(sc, r0Id, false, false); + ShortResource resource = restExtJsService.getExtResource(sc, r0Id, false, false, false); assertEquals(RES_NAME, resource.getName()); assertEquals("u0", resource.getCreator()); assertEquals("u0", resource.getEditor()); @@ -306,7 +309,7 @@ public void testGetAllResources_editorUpdate() throws Exception { realResource.setName("new name"); restResourceService.update(sc, r0Id, createRESTResource(realResource)); - ShortResource resource = restExtJsService.getExtResource(sc, r0Id, false, false); + ShortResource resource = restExtJsService.getExtResource(sc, r0Id, false, false, false); assertEquals(realResource.getName(), resource.getName()); assertEquals("u0", resource.getCreator()); assertEquals("a0", resource.getEditor()); @@ -456,6 +459,7 @@ public void testExtResourcesList_sorted() throws Exception { new Sort("description", "asc"), false, false, + false, new AndFilter()); List resources = response.getList(); @@ -476,6 +480,7 @@ public void testExtResourcesList_sorted() throws Exception { new Sort("creation", "desc"), false, false, + false, new AndFilter()); List resources = response.getList(); @@ -489,7 +494,14 @@ public void testExtResourcesList_sorted() throws Exception { { ExtResourceList response = restExtJsService.getExtResourcesList( - sc, 0, 1000, new Sort(null, null), false, false, new AndFilter()); + sc, + 0, + 1000, + new Sort(null, null), + false, + false, + false, + new AndFilter()); List resources = response.getList(); assertEquals(3, resources.size()); @@ -508,6 +520,7 @@ public void testExtResourcesList_sorted() throws Exception { new Sort("unknownfield", "desc"), false, false, + false, new AndFilter()); assertNull(response); @@ -542,7 +555,7 @@ public void testExtResourcesList_creatorFiltered() throws Exception { ExtResourceList response = restExtJsService.getExtResourcesList( - sc, 0, 100, new Sort("", ""), false, false, editorFieldFilter); + sc, 0, 100, new Sort("", ""), false, false, false, editorFieldFilter); List resources = response.getList(); assertEquals(1, resources.size()); @@ -556,7 +569,7 @@ public void testExtResourcesList_creatorFiltered() throws Exception { ExtResourceList response = restExtJsService.getExtResourcesList( - sc, 0, 100, new Sort("", ""), false, false, editorFieldFilter); + sc, 0, 100, new Sort("", ""), false, false, false, editorFieldFilter); List resources = response.getList(); assertEquals(2, resources.size()); @@ -568,7 +581,7 @@ public void testExtResourcesList_creatorFiltered() throws Exception { ExtResourceList response = restExtJsService.getExtResourcesList( - sc, 0, 100, new Sort("", ""), false, false, editorFieldFilter); + sc, 0, 100, new Sort("", ""), false, false, false, editorFieldFilter); assertTrue(response.isEmpty()); } @@ -602,7 +615,7 @@ public void testExtResourcesList_editorFiltered() throws Exception { ExtResourceList response = restExtJsService.getExtResourcesList( - sc, 0, 100, new Sort("", ""), false, false, editorFieldFilter); + sc, 0, 100, new Sort("", ""), false, false, false, editorFieldFilter); List resources = response.getList(); assertEquals(1, resources.size()); @@ -616,7 +629,7 @@ public void testExtResourcesList_editorFiltered() throws Exception { ExtResourceList response = restExtJsService.getExtResourcesList( - sc, 0, 100, new Sort("", ""), false, false, editorFieldFilter); + sc, 0, 100, new Sort("", ""), false, false, false, editorFieldFilter); List resources = response.getList(); assertEquals(2, resources.size()); @@ -628,7 +641,7 @@ public void testExtResourcesList_editorFiltered() throws Exception { ExtResourceList response = restExtJsService.getExtResourcesList( - sc, 0, 100, new Sort("", ""), false, false, editorFieldFilter); + sc, 0, 100, new Sort("", ""), false, false, false, editorFieldFilter); assertTrue(response.isEmpty()); } @@ -672,12 +685,11 @@ public void testExtResourcesList_groupFiltered() throws Exception { { /* search for name equality of a single group */ - GroupFilter groupFilter = - new GroupFilter(Collections.singletonList("groupA"), SearchOperator.EQUAL_TO); + GroupFilter groupFilter = new GroupFilter("groupA", SearchOperator.EQUAL_TO); ExtResourceList response = restExtJsService.getExtResourcesList( - sc, 0, 100, new Sort("", ""), false, false, groupFilter); + sc, 0, 100, new Sort("", ""), false, false, false, groupFilter); List resources = response.getList(); assertEquals(1, resources.size()); @@ -687,12 +699,11 @@ public void testExtResourcesList_groupFiltered() throws Exception { { /* search for name similarity (ignoring case) of multiple groups */ - GroupFilter groupFilter = - new GroupFilter(Collections.singletonList("GROUP_"), SearchOperator.ILIKE); + GroupFilter groupFilter = new GroupFilter("GROUP_", SearchOperator.ILIKE); ExtResourceList response = restExtJsService.getExtResourcesList( - sc, 0, 100, new Sort("", ""), false, false, groupFilter); + sc, 0, 100, new Sort("", ""), false, false, false, groupFilter); List resources = response.getList(); assertEquals(2, resources.size()); @@ -700,97 +711,272 @@ public void testExtResourcesList_groupFiltered() throws Exception { { /* search for name equality of multiple groups */ - GroupFilter groupFilter = - new GroupFilter(List.of("groupA", "groupB", "groupC"), SearchOperator.IN); + GroupFilter groupFilter = new GroupFilter("groupA,groupB,groupC", SearchOperator.IN); ExtResourceList response = restExtJsService.getExtResourcesList( - sc, 0, 100, new Sort("", ""), false, false, groupFilter); + sc, 0, 100, new Sort("", ""), false, false, false, groupFilter); List resources = response.getList(); assertEquals(2, resources.size()); } { - /* erroneous search for similarity of multiple groups */ - GroupFilter groupFilter = new GroupFilter(List.of("a", "b"), SearchOperator.LIKE); + /* search for equality in empty group list */ + GroupFilter groupFilter = new GroupFilter("", SearchOperator.EQUAL_TO); - assertThrows( - IllegalStateException.class, - () -> - restExtJsService.getExtResourcesList( - sc, 0, 100, new Sort("", ""), false, false, groupFilter)); + ExtResourceList response = + restExtJsService.getExtResourcesList( + sc, 0, 100, new Sort("", ""), false, false, false, groupFilter); + assertTrue(response.getList().isEmpty()); } { - /* erroneous search for equality in empty group list */ - GroupFilter groupFilter = - new GroupFilter(Collections.emptyList(), SearchOperator.EQUAL_TO); + /* unknown group */ + GroupFilter groupFilter = new GroupFilter("unknown group", SearchOperator.EQUAL_TO); + + ExtResourceList response = + restExtJsService.getExtResourcesList( + sc, 0, 100, new Sort("", ""), false, false, false, groupFilter); + + assertTrue(response.getList().isEmpty()); + } + { + /* invalid filter */ assertThrows( - IllegalStateException.class, - () -> - restExtJsService.getExtResourcesList( - sc, 0, 100, new Sort("", ""), false, false, groupFilter)); + IllegalArgumentException.class, () -> new GroupFilter(null, SearchOperator.IN)); } + } + + @Test + public void testExtResourcesList_filteredForGroupNameWithCommas() throws Exception { + final String CAT0_NAME = "CAT000"; + final String RESOURCE_A_NAME = "resourceA"; + final String RESOURCE_B_NAME = "resourceB"; + final String GROUP_NAME_WITH_COMMAS = ",groupA,B,C,"; + final String OTHER_GROUP_NAME = "group"; + + long user0Id = restCreateUser("u0", Role.USER, null, "p0"); + SecurityContext sc = new SimpleSecurityContext(user0Id); + + createCategory(CAT0_NAME); + + long resourceAId = restCreateResource(RESOURCE_A_NAME, "", CAT0_NAME, user0Id, true); + long resourceBId = restCreateResource(RESOURCE_B_NAME, "", CAT0_NAME, user0Id, true); + + SecurityRule securityRuleGroupA = new SecurityRule(); + securityRuleGroupA.setGroup(userGroupService.get(createGroup(GROUP_NAME_WITH_COMMAS))); + securityRuleGroupA.setCanWrite(true); + + List securityRulesResourceA = resourceService.getSecurityRules(resourceAId); + securityRulesResourceA.add(securityRuleGroupA); + restResourceService.updateSecurityRules( + sc, resourceAId, new SecurityRuleList(securityRulesResourceA)); + + SecurityRule securityRuleGroupB = new SecurityRule(); + securityRuleGroupB.setGroup(userGroupService.get(createGroup(OTHER_GROUP_NAME))); + securityRuleGroupB.setCanRead(true); + + List securityRulesResourceB = resourceService.getSecurityRules(resourceBId); + securityRulesResourceB.add(securityRuleGroupB); + restResourceService.updateSecurityRules( + sc, resourceBId, new SecurityRuleList(securityRulesResourceB)); { - /* unknown group */ - GroupFilter groupFilter = - new GroupFilter( - Collections.singletonList("unknown group"), SearchOperator.EQUAL_TO); + /* search for name equality */ + GroupFilter groupFilter = new GroupFilter(",groupA,B,C,", SearchOperator.EQUAL_TO); ExtResourceList response = restExtJsService.getExtResourcesList( - sc, 0, 100, new Sort("", ""), false, false, groupFilter); + sc, 0, 100, new Sort("", ""), false, false, false, groupFilter); - assertTrue(response.getList().isEmpty()); + List resources = response.getList(); + assertEquals(1, resources.size()); + Resource resource = resources.get(0); + assertEquals(RESOURCE_A_NAME, resource.getName()); + } + + { + /* search for name similarity */ + GroupFilter groupFilter = new GroupFilter(",%,", SearchOperator.ILIKE); + + ExtResourceList response = + restExtJsService.getExtResourcesList( + sc, 0, 100, new Sort("", ""), false, false, false, groupFilter); + + List resources = response.getList(); + assertEquals(1, resources.size()); + Resource resource = resources.get(0); + assertEquals(RESOURCE_A_NAME, resource.getName()); + } + + { + /* search for name equality of multiple groups */ + GroupFilter groupFilter = new GroupFilter("group,\",groupA,B,C,\"", SearchOperator.IN); + + ExtResourceList response = + restExtJsService.getExtResourcesList( + sc, 0, 100, new Sort("", ""), false, false, false, groupFilter); + + List resources = response.getList(); + assertEquals(2, resources.size()); } } @Test - public void testExtResourcesList_groupFilteredWithInvalidInFilter() throws Exception { + public void testExtResourcesList_tagFiltered() throws Exception { final String CAT0_NAME = "CAT000"; + final String RESOURCE_A_NAME = "resourceA"; + final String RESOURCE_B_NAME = "resourceB"; + final String TAG_A_NAME = "tagA"; + final String TAG_B_NAME = "tagB"; long user0Id = restCreateUser("u0", Role.USER, null, "p0"); SecurityContext sc = new SimpleSecurityContext(user0Id); + long tagAId = tagService.insert(new Tag(TAG_A_NAME, "", null)); + long tagBId = tagService.insert(new Tag(TAG_B_NAME, "", null)); + createCategory(CAT0_NAME); - restCreateResource("resourceA", "description_A", CAT0_NAME, user0Id, true); + long resourceAId = + restCreateResource(RESOURCE_A_NAME, "description_A", CAT0_NAME, user0Id, true); + long resourceBId = + restCreateResource(RESOURCE_B_NAME, "description_B", CAT0_NAME, user0Id, true); + + tagService.addToResource(tagAId, resourceAId); + tagService.addToResource(tagBId, resourceAId); + + tagService.addToResource(tagBId, resourceBId); { - GroupFilter groupFilter = new GroupFilter(null, SearchOperator.IN); + /* search for name equality of a single tag */ + TagFilter tagFilter = new TagFilter("tagA", SearchOperator.EQUAL_TO); ExtResourceList response = restExtJsService.getExtResourcesList( - sc, 0, 100, new Sort("", ""), false, false, groupFilter); + sc, 0, 100, new Sort("", ""), false, false, false, tagFilter); List resources = response.getList(); assertEquals(1, resources.size()); + Resource resource = resources.get(0); + assertEquals(RESOURCE_A_NAME, resource.getName()); + } + + { + /* search for name similarity (ignoring case) of multiple tags */ + TagFilter tagFilter = new TagFilter("TAG_", SearchOperator.ILIKE); + + ExtResourceList response = + restExtJsService.getExtResourcesList( + sc, 0, 100, new Sort("", ""), false, false, false, tagFilter); + + List resources = response.getList(); + assertEquals(2, resources.size()); } { - GroupFilter groupFilter = - new GroupFilter(Collections.singletonList(""), SearchOperator.IN); + /* search for name equality of multiple tags */ + TagFilter tagFilter = new TagFilter("tagA,tagB,TagC", SearchOperator.IN); ExtResourceList response = restExtJsService.getExtResourcesList( - sc, 0, 100, new Sort("", ""), false, false, groupFilter); + sc, 0, 100, new Sort("", ""), false, false, false, tagFilter); + + List resources = response.getList(); + assertEquals(2, resources.size()); + } + { + /* search for equality in empty tag list */ + TagFilter tagFilter = new TagFilter("", SearchOperator.EQUAL_TO); + + ExtResourceList response = + restExtJsService.getExtResourcesList( + sc, 0, 100, new Sort("", ""), false, false, false, tagFilter); assertTrue(response.getList().isEmpty()); } { - GroupFilter groupFilter = - new GroupFilter(Collections.singletonList(null), SearchOperator.IN); + /* unknown tag */ + TagFilter tagFilter = new TagFilter("unknown tag", SearchOperator.EQUAL_TO); ExtResourceList response = restExtJsService.getExtResourcesList( - sc, 0, 100, new Sort("", ""), false, false, groupFilter); + sc, 0, 100, new Sort("", ""), false, false, false, tagFilter); assertTrue(response.getList().isEmpty()); } + + { + /* invalid filter */ + assertThrows( + IllegalArgumentException.class, () -> new TagFilter(null, SearchOperator.IN)); + } + } + + @Test + public void testExtResourcesList_filteredForTagNameWithCommas() throws Exception { + final String CAT0_NAME = "CAT000"; + final String RESOURCE_A_NAME = "resourceA"; + final String RESOURCE_B_NAME = "resourceB"; + + long user0Id = restCreateUser("u0", Role.USER, null, "p0"); + SecurityContext sc = new SimpleSecurityContext(user0Id); + + long tagWithCommas = tagService.insert(new Tag(",a,b,c,d,", "", null)); + long tag = tagService.insert(new Tag("tag", "", null)); + + createCategory(CAT0_NAME); + + long resourceAId = restCreateResource(RESOURCE_A_NAME, "", CAT0_NAME, user0Id, true); + long resourceBId = restCreateResource(RESOURCE_B_NAME, "", CAT0_NAME, user0Id, true); + + tagService.addToResource(tagWithCommas, resourceAId); + tagService.addToResource(tag, resourceAId); + + tagService.addToResource(tag, resourceBId); + + { + /* search for name equality */ + TagFilter tagFilter = new TagFilter(",a,b,c,d,", SearchOperator.EQUAL_TO); + + ExtResourceList response = + restExtJsService.getExtResourcesList( + sc, 0, 100, new Sort("", ""), false, false, false, tagFilter); + + List resources = response.getList(); + assertEquals(1, resources.size()); + Resource resource = resources.get(0); + assertEquals(RESOURCE_A_NAME, resource.getName()); + } + + { + /* search for name similarity */ + TagFilter tagFilter = new TagFilter(",%,", SearchOperator.ILIKE); + + ExtResourceList response = + restExtJsService.getExtResourcesList( + sc, 0, 100, new Sort("", ""), false, false, false, tagFilter); + + List resources = response.getList(); + assertEquals(1, resources.size()); + Resource resource = resources.get(0); + assertEquals(RESOURCE_A_NAME, resource.getName()); + } + + { + /* search for name equality of multiple tags */ + TagFilter tagFilter = new TagFilter("tag,\",a,b,c,d,\"", SearchOperator.IN); + + ExtResourceList response = + restExtJsService.getExtResourcesList( + sc, 0, 100, new Sort("", ""), false, false, false, tagFilter); + + List resources = response.getList(); + assertEquals(2, resources.size()); + } } @Test @@ -822,7 +1008,7 @@ public void testExtResourcesList_timeAttributesFiltered() throws Exception { ExtResourceList response = restExtJsService.getExtResourcesList( - sc, 0, 100, new Sort("", ""), false, false, ltDateFilter); + sc, 0, 100, new Sort("", ""), false, false, false, ltDateFilter); List resources = response.getList(); assertEquals(1, resources.size()); @@ -847,7 +1033,14 @@ public void testExtResourcesList_timeAttributesFiltered() throws Exception { ExtResourceList response = restExtJsService.getExtResourcesList( - sc, 0, 100, new Sort("", ""), false, false, betweenDatesFieldFilter); + sc, + 0, + 100, + new Sort("", ""), + false, + false, + false, + betweenDatesFieldFilter); List resources = response.getList(); assertEquals(2, resources.size()); @@ -906,6 +1099,7 @@ public void testExtResourcesList_userOwnedWithPermissionsInformation() throws Ex new Sort("", ""), false, false, + false, new AndFilter()); List resources = response.getList(); assertEquals(5, resources.size()); @@ -923,6 +1117,7 @@ public void testExtResourcesList_userOwnedWithPermissionsInformation() throws Ex new Sort("", ""), false, false, + false, new AndFilter()); List resources = response.getList(); assertEquals(2, resources.size()); @@ -1016,6 +1211,7 @@ public void testExtResourcesList_groupOwnedResourceWithPermissionsInformation() new Sort("", ""), false, false, + false, new AndFilter()); List resources = response.getList(); assertEquals(3, resources.size()); @@ -1033,6 +1229,7 @@ public void testExtResourcesList_groupOwnedResourceWithPermissionsInformation() new Sort("", ""), false, false, + false, new AndFilter()); List resources = response.getList(); assertEquals(2, resources.size()); @@ -1057,6 +1254,60 @@ public void testExtResourcesList_groupOwnedResourceWithPermissionsInformation() } } + @Test + public void testExtResourcesList_withTags() throws Exception { + final String CAT0_NAME = "CAT000"; + Tag tagA = new Tag("tagA", "#4561aa", "dusky"); + Tag tagB = new Tag("tagB", "magenta", null); + + long userId = restCreateUser("u0", Role.USER, null, "p0"); + SecurityContext userSecurityContext = new SimpleSecurityContext(userId); + + createCategory(CAT0_NAME); + + long resourceId = restCreateResource("ownedResource", "", CAT0_NAME, userId, false); + long tagAId = tagService.insert(tagA); + long tagBId = tagService.insert(tagB); + + tagService.addToResource(tagAId, resourceId); + tagService.addToResource(tagBId, resourceId); + + { + ExtResourceList response = + restExtJsService.getExtResourcesList( + userSecurityContext, + 0, + 1000, + new Sort("", ""), + false, + false, + true, + new AndFilter()); + List resources = response.getList(); + assertEquals(1, resources.size()); + ExtResource extResource = resources.get(0); + Collection tags = extResource.getTags(); + assertEquals(2, tags.size()); + } + + { + ExtResourceList response = + restExtJsService.getExtResourcesList( + userSecurityContext, + 0, + 1000, + new Sort("", ""), + false, + false, + false, + new AndFilter()); + List resources = response.getList(); + assertEquals(1, resources.size()); + ExtResource extResource = resources.get(0); + assertNull(extResource.getTags()); + } + } + @Test public void testGetExtResource_userOwnedWithAttributesInformation() throws Exception { final String CAT0_NAME = "CAT000"; @@ -1116,7 +1367,7 @@ public void testGetExtResource_userOwnedWithAttributesInformation() throws Excep { ExtShortResource response = restExtJsService.getExtResource( - adminSecurityContext, userOwnedResourceId, true, true); + adminSecurityContext, userOwnedResourceId, true, true, false); assertTrue(response.isCanEdit()); assertTrue(response.isCanDelete()); List attributes = response.getAttributeList().getList(); @@ -1130,7 +1381,7 @@ public void testGetExtResource_userOwnedWithAttributesInformation() throws Excep { ExtShortResource response = restExtJsService.getExtResource( - adminSecurityContext, userOwnedResourceId, false, false); + adminSecurityContext, userOwnedResourceId, false, false, false); List attributes = response.getAttributeList().getList(); assertNull(attributes); } @@ -1138,7 +1389,7 @@ public void testGetExtResource_userOwnedWithAttributesInformation() throws Excep { ExtShortResource response = restExtJsService.getExtResource( - user0SecurityContext, userOwnedResourceId, true, true); + user0SecurityContext, userOwnedResourceId, true, true, false); assertTrue(response.isCanEdit()); assertTrue(response.isCanDelete()); List attributes = response.getAttributeList().getList(); @@ -1152,7 +1403,7 @@ public void testGetExtResource_userOwnedWithAttributesInformation() throws Excep { ExtShortResource response = restExtJsService.getExtResource( - user0SecurityContext, readOnlyResourceId, true, true); + user0SecurityContext, readOnlyResourceId, true, true, false); assertFalse(response.isCanEdit()); assertFalse(response.isCanDelete()); List attributes = response.getAttributeList().getList(); @@ -1166,7 +1417,7 @@ public void testGetExtResource_userOwnedWithAttributesInformation() throws Excep { ExtShortResource response = restExtJsService.getExtResource( - user0SecurityContext, noAttributesResourceId, true, true); + user0SecurityContext, noAttributesResourceId, true, true, false); List attributes = response.getAttributeList().getList(); assertTrue(attributes.isEmpty()); } @@ -1176,7 +1427,7 @@ public void testGetExtResource_userOwnedWithAttributesInformation() throws Excep ForbiddenErrorWebEx.class, () -> restExtJsService.getExtResource( - user0SecurityContext, protectedResourceId, true, true)); + user0SecurityContext, protectedResourceId, true, true, false)); } { @@ -1184,7 +1435,7 @@ public void testGetExtResource_userOwnedWithAttributesInformation() throws Excep NotFoundWebEx.class, () -> restExtJsService.getExtResource( - user0SecurityContext, Long.MAX_VALUE, true, true)); + user0SecurityContext, Long.MAX_VALUE, true, true, false)); } } @@ -1264,7 +1515,7 @@ public void testGetExtResource_groupOwnedWithAttributesInformation() throws Exce { ExtShortResource response = restExtJsService.getExtResource( - adminSecurityContext, groupOwnedResourceId, true, true); + adminSecurityContext, groupOwnedResourceId, true, true, false); List attributes = response.getAttributeList().getList(); assertEquals(1, attributes.size()); ShortAttribute attribute = attributes.get(0); @@ -1276,7 +1527,7 @@ public void testGetExtResource_groupOwnedWithAttributesInformation() throws Exce { ExtShortResource response = restExtJsService.getExtResource( - user0SecurityContext, groupOwnedResourceId, true, true); + user0SecurityContext, groupOwnedResourceId, true, true, false); List attributes = response.getAttributeList().getList(); assertEquals(1, attributes.size()); ShortAttribute attribute = attributes.get(0); @@ -1288,7 +1539,7 @@ public void testGetExtResource_groupOwnedWithAttributesInformation() throws Exce { ExtShortResource response = restExtJsService.getExtResource( - user0SecurityContext, readOnlyGroupResourceId, true, true); + user0SecurityContext, readOnlyGroupResourceId, true, true, false); assertFalse(response.isCanEdit()); assertFalse(response.isCanDelete()); List attributes = response.getAttributeList().getList(); @@ -1304,7 +1555,11 @@ public void testGetExtResource_groupOwnedWithAttributesInformation() throws Exce ForbiddenErrorWebEx.class, () -> restExtJsService.getExtResource( - user0SecurityContext, protectedGroupResourceId, true, true)); + user0SecurityContext, + protectedGroupResourceId, + true, + true, + false)); } } @@ -1356,7 +1611,7 @@ public void testGetExtResource_userOwnedWithPermissionsInformation() throws Exce { ExtShortResource response = restExtJsService.getExtResource( - adminSecurityContext, noPermissionsResourceId, true, true); + adminSecurityContext, noPermissionsResourceId, true, true, false); List securityRules = response.getSecurityRuleList().getList(); assertTrue(securityRules.isEmpty()); } @@ -1364,7 +1619,7 @@ public void testGetExtResource_userOwnedWithPermissionsInformation() throws Exce { ExtShortResource response = restExtJsService.getExtResource( - adminSecurityContext, userOwnedResourceId, true, true); + adminSecurityContext, userOwnedResourceId, true, true, false); assertTrue(response.isCanEdit()); assertTrue(response.isCanDelete()); List securityRules = response.getSecurityRuleList().getList(); @@ -1374,7 +1629,7 @@ public void testGetExtResource_userOwnedWithPermissionsInformation() throws Exce { ExtShortResource response = restExtJsService.getExtResource( - adminSecurityContext, userOwnedResourceId, false, false); + adminSecurityContext, userOwnedResourceId, false, false, false); List securityRules = response.getSecurityRuleList().getList(); assertNull(securityRules); } @@ -1382,7 +1637,7 @@ public void testGetExtResource_userOwnedWithPermissionsInformation() throws Exce { ExtShortResource response = restExtJsService.getExtResource( - user0SecurityContext, userOwnedResourceId, true, true); + user0SecurityContext, userOwnedResourceId, true, true, false); assertTrue(response.isCanEdit()); assertTrue(response.isCanDelete()); List securityRules = response.getSecurityRuleList().getList(); @@ -1396,7 +1651,7 @@ public void testGetExtResource_userOwnedWithPermissionsInformation() throws Exce { ExtShortResource response = restExtJsService.getExtResource( - user0SecurityContext, readOnlyResourceId, true, true); + user0SecurityContext, readOnlyResourceId, true, true, false); assertFalse(response.isCanEdit()); assertFalse(response.isCanDelete()); List securityRules = response.getSecurityRuleList().getList(); @@ -1412,7 +1667,7 @@ public void testGetExtResource_userOwnedWithPermissionsInformation() throws Exce ForbiddenErrorWebEx.class, () -> restExtJsService.getExtResource( - user0SecurityContext, protectedResourceId, true, true)); + user0SecurityContext, protectedResourceId, true, true, false)); } { @@ -1420,7 +1675,7 @@ public void testGetExtResource_userOwnedWithPermissionsInformation() throws Exce NotFoundWebEx.class, () -> restExtJsService.getExtResource( - user0SecurityContext, Long.MAX_VALUE, true, true)); + user0SecurityContext, Long.MAX_VALUE, true, true, false)); } } @@ -1478,7 +1733,7 @@ public void testGetExtResource_groupOwnedWithPermissionsInformation() throws Exc { ExtShortResource response = restExtJsService.getExtResource( - adminSecurityContext, groupOwnedResourceId, true, true); + adminSecurityContext, groupOwnedResourceId, true, true, false); assertTrue(response.isCanEdit()); assertTrue(response.isCanDelete()); List securityRules = response.getSecurityRuleList().getList(); @@ -1488,7 +1743,7 @@ public void testGetExtResource_groupOwnedWithPermissionsInformation() throws Exc { ExtShortResource response = restExtJsService.getExtResource( - user0SecurityContext, groupOwnedResourceId, true, true); + user0SecurityContext, groupOwnedResourceId, true, true, false); assertTrue(response.isCanEdit()); assertTrue(response.isCanDelete()); List securityRules = response.getSecurityRuleList().getList(); @@ -1502,7 +1757,7 @@ public void testGetExtResource_groupOwnedWithPermissionsInformation() throws Exc { ExtShortResource response = restExtJsService.getExtResource( - user0SecurityContext, readOnlyGroupResourceId, true, true); + user0SecurityContext, readOnlyGroupResourceId, true, true, false); assertFalse(response.isCanEdit()); assertFalse(response.isCanDelete()); List securityRules = response.getSecurityRuleList().getList(); @@ -1518,7 +1773,45 @@ public void testGetExtResource_groupOwnedWithPermissionsInformation() throws Exc ForbiddenErrorWebEx.class, () -> restExtJsService.getExtResource( - user0SecurityContext, protectedGroupResourceId, true, true)); + user0SecurityContext, + protectedGroupResourceId, + true, + true, + false)); + } + } + + @Test + public void testGetExtResource_withTags() throws Exception { + final String CAT0_NAME = "CAT000"; + Tag tagA = new Tag("tagA", "#4561aa", "dusky"); + Tag tagB = new Tag("tagB", "magenta", null); + + long userId = restCreateUser("u0", Role.USER, null, "p0"); + SecurityContext userSecurityContext = new SimpleSecurityContext(userId); + + createCategory(CAT0_NAME); + + long resourceId = restCreateResource("ownedResource", "", CAT0_NAME, userId, false); + long tagAId = tagService.insert(tagA); + long tagBId = tagService.insert(tagB); + + tagService.addToResource(tagAId, resourceId); + tagService.addToResource(tagBId, resourceId); + + { + ExtShortResource response = + restExtJsService.getExtResource( + userSecurityContext, resourceId, false, false, true); + Collection tags = response.getTagList().getList(); + assertEquals(2, tags.size()); + } + + { + ExtShortResource response = + restExtJsService.getExtResource( + userSecurityContext, resourceId, false, false, false); + assertNull(response.getTagList().getList()); } } diff --git a/src/modules/rest/extjs/src/test/java/it/geosolutions/geostore/services/rest/impl/ServiceTestBase.java b/src/modules/rest/extjs/src/test/java/it/geosolutions/geostore/services/rest/impl/ServiceTestBase.java index 827d4165..2dfcd181 100644 --- a/src/modules/rest/extjs/src/test/java/it/geosolutions/geostore/services/rest/impl/ServiceTestBase.java +++ b/src/modules/rest/extjs/src/test/java/it/geosolutions/geostore/services/rest/impl/ServiceTestBase.java @@ -68,6 +68,7 @@ public class ServiceTestBase { protected static CategoryService categoryService; protected static UserService userService; protected static UserGroupService userGroupService; + protected static TagService tagService; protected static ResourcePermissionService resourcePermissionService; protected static ResourceDAO resourceDAO; @@ -95,6 +96,7 @@ public ServiceTestBase() { categoryService = (CategoryService) ctx.getBean("categoryService"); userService = (UserService) ctx.getBean("userService"); userGroupService = (UserGroupService) ctx.getBean("userGroupService"); + tagService = (TagService) ctx.getBean("tagService"); resourcePermissionService = (ResourcePermissionService) ctx.getBean("resourcePermissionService"); @@ -105,7 +107,7 @@ public ServiceTestBase() { } @Before - protected void setUp() throws Exception { + public void setUp() throws Exception { testCheckServices(); LOGGER.info( @@ -126,6 +128,7 @@ public void testCheckServices() { assertNotNull(categoryService); assertNotNull(userService); assertNotNull(userGroupService); + assertNotNull(tagService); assertNotNull(resourcePermissionService); assertNotNull(resourceDAO); @@ -139,6 +142,7 @@ public void testCheckServices() { protected void removeAll() throws NotFoundServiceEx, BadRequestServiceEx, InternalErrorServiceEx { LOGGER.info("***** removeAll()"); + removeAllTag(); removeAllResource(); removeAllStoredData(); removeAllCategory(); @@ -146,6 +150,20 @@ protected void removeAll() removeAllUserGroup(); } + private void removeAllTag() throws BadRequestServiceEx { + tagService + .getAll(null, null, null) + .forEach( + item -> { + LOGGER.info("Removing tag: {}", item.getName()); + try { + tagService.delete(item.getId()); + } catch (NotFoundServiceEx e) { + throw new RuntimeException(e); + } + }); + } + /** * @throws BadRequestServiceEx * @throws NotFoundServiceEx diff --git a/src/modules/rest/impl/src/main/java/it/geosolutions/geostore/services/rest/impl/RESTCategoryServiceImpl.java b/src/modules/rest/impl/src/main/java/it/geosolutions/geostore/services/rest/impl/RESTCategoryServiceImpl.java index cbf56f98..6ac6ef30 100644 --- a/src/modules/rest/impl/src/main/java/it/geosolutions/geostore/services/rest/impl/RESTCategoryServiceImpl.java +++ b/src/modules/rest/impl/src/main/java/it/geosolutions/geostore/services/rest/impl/RESTCategoryServiceImpl.java @@ -201,7 +201,6 @@ public CategoryList getAll(SecurityContext sc, Integer page, Integer entries) */ @Override public long getCount(SecurityContext sc, String nameLike) { - nameLike = nameLike.replaceAll("[*]", "%"); - return categoryService.getCount(nameLike); + return categoryService.getCount(convertNameLikeToSqlSyntax(nameLike)); } } diff --git a/src/modules/rest/impl/src/main/java/it/geosolutions/geostore/services/rest/impl/RESTResourceServiceImpl.java b/src/modules/rest/impl/src/main/java/it/geosolutions/geostore/services/rest/impl/RESTResourceServiceImpl.java index bf8f9e1d..d4ee7264 100644 --- a/src/modules/rest/impl/src/main/java/it/geosolutions/geostore/services/rest/impl/RESTResourceServiceImpl.java +++ b/src/modules/rest/impl/src/main/java/it/geosolutions/geostore/services/rest/impl/RESTResourceServiceImpl.java @@ -28,7 +28,11 @@ package it.geosolutions.geostore.services.rest.impl; -import it.geosolutions.geostore.core.model.*; +import it.geosolutions.geostore.core.model.Attribute; +import it.geosolutions.geostore.core.model.Category; +import it.geosolutions.geostore.core.model.Resource; +import it.geosolutions.geostore.core.model.SecurityRule; +import it.geosolutions.geostore.core.model.User; import it.geosolutions.geostore.core.model.enums.DataType; import it.geosolutions.geostore.core.model.enums.Role; import it.geosolutions.geostore.services.ResourceService; @@ -44,8 +48,18 @@ import it.geosolutions.geostore.services.exception.InternalErrorServiceEx; import it.geosolutions.geostore.services.exception.NotFoundServiceEx; import it.geosolutions.geostore.services.rest.RESTResourceService; -import it.geosolutions.geostore.services.rest.exception.*; -import it.geosolutions.geostore.services.rest.model.*; +import it.geosolutions.geostore.services.rest.exception.BadRequestWebEx; +import it.geosolutions.geostore.services.rest.exception.ConflictWebEx; +import it.geosolutions.geostore.services.rest.exception.ForbiddenErrorWebEx; +import it.geosolutions.geostore.services.rest.exception.InternalErrorWebEx; +import it.geosolutions.geostore.services.rest.exception.NotFoundWebEx; +import it.geosolutions.geostore.services.rest.model.RESTAttribute; +import it.geosolutions.geostore.services.rest.model.RESTCategory; +import it.geosolutions.geostore.services.rest.model.RESTResource; +import it.geosolutions.geostore.services.rest.model.ResourceList; +import it.geosolutions.geostore.services.rest.model.SecurityRuleList; +import it.geosolutions.geostore.services.rest.model.ShortAttributeList; +import it.geosolutions.geostore.services.rest.model.ShortResourceList; import it.geosolutions.geostore.services.rest.utils.Convert; import java.util.ArrayList; import java.util.List; @@ -308,14 +322,12 @@ public Resource get(SecurityContext sc, long id, boolean fullResource) throws No public ShortResourceList getList( SecurityContext sc, String nameLike, Integer page, Integer entries) throws BadRequestWebEx { - User authUser = extractAuthUser(sc); - nameLike = nameLike.replaceAll("[*]", "%"); - try { + User authUser = extractAuthUser(sc); return new ShortResourceList( resourceService.getList( ResourceSearchParameters.builder() - .nameLike(nameLike) + .nameLike(convertNameLikeToSqlSyntax(nameLike)) .page(page) .entries(entries) .authUser(authUser) @@ -355,8 +367,7 @@ public ShortResourceList getAll(SecurityContext sc, Integer page, Integer entrie */ @Override public long getCount(SecurityContext sc, String nameLike) { - nameLike = nameLike.replaceAll("[*]", "%"); - return resourceService.getCount(nameLike); + return resourceService.getCount(convertNameLikeToSqlSyntax(nameLike)); } /** diff --git a/src/modules/rest/impl/src/main/java/it/geosolutions/geostore/services/rest/impl/RESTServiceImpl.java b/src/modules/rest/impl/src/main/java/it/geosolutions/geostore/services/rest/impl/RESTServiceImpl.java index f3105925..38e333ed 100644 --- a/src/modules/rest/impl/src/main/java/it/geosolutions/geostore/services/rest/impl/RESTServiceImpl.java +++ b/src/modules/rest/impl/src/main/java/it/geosolutions/geostore/services/rest/impl/RESTServiceImpl.java @@ -71,7 +71,7 @@ public abstract class RESTServiceImpl { * @param groups * @return */ - public static List extratcGroupNames(Set groups) { + public static List extractGroupNames(Set groups) { List groupNames = new ArrayList<>(groups.size() + 1); for (UserGroup ug : groups) { groupNames.add(ug.getGroupName()); @@ -182,7 +182,7 @@ public boolean resourceAccessWrite(User authUser, long resourceId) { } } - List groupNames = extratcGroupNames(authUser.getGroups()); + List groupNames = extractGroupNames(authUser.getGroups()); if (!groupNames.isEmpty()) { List groupSecurityRules = getSecurityService().getGroupSecurityRule(groupNames, resourceId); @@ -231,7 +231,7 @@ public boolean resourceAccessRead(User authUser, long resourceId) { } } - List groupNames = extratcGroupNames(authUser.getGroups()); + List groupNames = extractGroupNames(authUser.getGroups()); if (!groupNames.isEmpty()) { List groupSecurityRules = getSecurityService().getGroupSecurityRule(groupNames, resourceId); @@ -278,4 +278,11 @@ public Principal createGuestPrincipal() { guest.setGroups(groups); return new UsernamePasswordAuthenticationToken(guest, "", authorities); } + + public static String convertNameLikeToSqlSyntax(String nameLike) { + if (nameLike == null) { + return null; + } + return nameLike.replaceAll("[*]", "%"); + } } diff --git a/src/modules/rest/impl/src/main/java/it/geosolutions/geostore/services/rest/impl/RESTTagServiceImpl.java b/src/modules/rest/impl/src/main/java/it/geosolutions/geostore/services/rest/impl/RESTTagServiceImpl.java new file mode 100644 index 00000000..030df644 --- /dev/null +++ b/src/modules/rest/impl/src/main/java/it/geosolutions/geostore/services/rest/impl/RESTTagServiceImpl.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2025 GeoSolutions S.A.S. + * http://www.geo-solutions.it + * + * GPLv3 + Classpath exception + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. + * + * ==================================================================== + * + * This software consists of voluntary contributions made by developers + * of GeoSolutions. For more information on GeoSolutions, please see + * . + * + */ +package it.geosolutions.geostore.services.rest.impl; + +import it.geosolutions.geostore.core.model.Tag; +import it.geosolutions.geostore.services.TagService; +import it.geosolutions.geostore.services.exception.BadRequestServiceEx; +import it.geosolutions.geostore.services.exception.NotFoundServiceEx; +import it.geosolutions.geostore.services.rest.RESTTagService; +import it.geosolutions.geostore.services.rest.exception.BadRequestWebEx; +import it.geosolutions.geostore.services.rest.exception.NotFoundWebEx; +import it.geosolutions.geostore.services.rest.model.TagList; +import java.util.List; +import javax.ws.rs.core.SecurityContext; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class RESTTagServiceImpl implements RESTTagService { + + private static final Logger LOGGER = LogManager.getLogger(RESTTagServiceImpl.class); + + private TagService tagService; + + public void setTagService(TagService tagService) { + this.tagService = tagService; + } + + @Override + public long insert(SecurityContext sc, Tag tag) { + try { + if (tag == null) throw new BadRequestWebEx("Tag is null"); + if (tag.getId() != null) throw new BadRequestWebEx("Id should be null"); + + return tagService.insert(tag); + } catch (BadRequestServiceEx e) { + LOGGER.error(e.getMessage(), e); + throw new BadRequestWebEx(e.getMessage()); + } + } + + @Override + public TagList getAll(SecurityContext sc, Integer page, Integer entries, String nameLike) + throws BadRequestWebEx { + try { + String sqlNameLike = RESTServiceImpl.convertNameLikeToSqlSyntax(nameLike); + List tags = tagService.getAll(page, entries, sqlNameLike); + + long count = 0; + if (!tags.isEmpty()) { + count = tagService.count(sqlNameLike); + } + + return new TagList(tags, count); + } catch (BadRequestServiceEx e) { + LOGGER.error(e.getMessage(), e); + throw new BadRequestWebEx(e.getMessage()); + } + } + + @Override + public Tag get(SecurityContext sc, long id) throws NotFoundWebEx { + Tag tag = tagService.get(id); + + if (tag == null) { + throw new NotFoundWebEx("Tag not found"); + } + + return tag; + } + + @Override + public long update(SecurityContext sc, long id, Tag tag) { + try { + return tagService.update(id, tag); + } catch (BadRequestServiceEx e) { + LOGGER.error(e.getMessage(), e); + throw new BadRequestWebEx(e.getMessage()); + } catch (NotFoundServiceEx e) { + throw new NotFoundWebEx(e.getMessage()); + } + } + + @Override + public void delete(SecurityContext sc, long id) throws NotFoundWebEx { + try { + tagService.delete(id); + } catch (NotFoundServiceEx e) { + throw new NotFoundWebEx(e.getMessage()); + } + } + + @Override + public void addToResource(SecurityContext sc, long id, long resourceId) throws NotFoundWebEx { + try { + tagService.addToResource(id, resourceId); + } catch (NotFoundServiceEx e) { + throw new NotFoundWebEx(e.getMessage()); + } + } + + @Override + public void removeFromResource(SecurityContext sc, long id, long resourceId) + throws NotFoundWebEx { + try { + tagService.removeFromResource(id, resourceId); + } catch (NotFoundServiceEx e) { + throw new NotFoundWebEx(e.getMessage()); + } + } +} diff --git a/src/modules/rest/impl/src/main/java/it/geosolutions/geostore/services/rest/impl/RESTUserServiceImpl.java b/src/modules/rest/impl/src/main/java/it/geosolutions/geostore/services/rest/impl/RESTUserServiceImpl.java index a74e6427..0b49dbd0 100644 --- a/src/modules/rest/impl/src/main/java/it/geosolutions/geostore/services/rest/impl/RESTUserServiceImpl.java +++ b/src/modules/rest/impl/src/main/java/it/geosolutions/geostore/services/rest/impl/RESTUserServiceImpl.java @@ -300,8 +300,7 @@ public UserList getAll(SecurityContext sc, Integer page, Integer entries) */ @Override public long getCount(SecurityContext sc, String nameLike) { - nameLike = nameLike.replaceAll("[*]", "%"); - return userService.getCount(nameLike); + return userService.getCount(convertNameLikeToSqlSyntax(nameLike)); } /* @@ -350,14 +349,13 @@ public UserList getUserList( Integer entries, boolean includeAttributes) throws BadRequestWebEx { - - nameLike = nameLike.replaceAll("[*]", "%"); - try { - List userList = userService.getAll(page, entries, nameLike, includeAttributes); + List userList = + userService.getAll( + page, entries, convertNameLikeToSqlSyntax(nameLike), includeAttributes); Iterator iterator = userList.iterator(); - List restUSERList = new ArrayList(); + List restUserList = new ArrayList<>(); while (iterator.hasNext()) { User user = iterator.next(); @@ -368,10 +366,10 @@ public UserList getUserList( user.getRole(), user.getGroups(), false); - restUSERList.add(restUser); + restUserList.add(restUser); } - return new UserList(restUSERList); + return new UserList(restUserList); } catch (BadRequestServiceEx ex) { throw new BadRequestWebEx(ex.getMessage()); } diff --git a/src/modules/rest/impl/src/main/resources/applicationContext.xml b/src/modules/rest/impl/src/main/resources/applicationContext.xml index eebff07e..30b49100 100644 --- a/src/modules/rest/impl/src/main/resources/applicationContext.xml +++ b/src/modules/rest/impl/src/main/resources/applicationContext.xml @@ -62,6 +62,9 @@ class="it.geosolutions.geostore.services.rest.impl.RESTSessionServiceImpl"> + + @@ -134,6 +137,7 @@ + diff --git a/src/modules/rest/impl/src/test/java/it/geosolutions/geostore/rest/service/impl/RESTTagServiceImplTest.java b/src/modules/rest/impl/src/test/java/it/geosolutions/geostore/rest/service/impl/RESTTagServiceImplTest.java new file mode 100644 index 00000000..2f0c16ff --- /dev/null +++ b/src/modules/rest/impl/src/test/java/it/geosolutions/geostore/rest/service/impl/RESTTagServiceImplTest.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2025 GeoSolutions S.A.S. + * http://www.geo-solutions.it + * + * GPLv3 + Classpath exception + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package it.geosolutions.geostore.rest.service.impl; + +import it.geosolutions.geostore.core.model.Tag; +import it.geosolutions.geostore.core.model.enums.Role; +import it.geosolutions.geostore.services.ServiceTestBase; +import it.geosolutions.geostore.services.exception.BadRequestServiceEx; +import it.geosolutions.geostore.services.exception.NotFoundServiceEx; +import it.geosolutions.geostore.services.rest.impl.RESTTagServiceImpl; +import it.geosolutions.geostore.services.rest.model.TagList; +import it.geosolutions.geostore.services.rest.utils.MockSecurityContext; +import java.util.List; +import javax.ws.rs.core.SecurityContext; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class RESTTagServiceImplTest extends ServiceTestBase { + + RESTTagServiceImpl restService; + + @Before + public void setUp() throws BadRequestServiceEx, NotFoundServiceEx { + restService = new RESTTagServiceImpl(); + restService.setTagService(tagService); + } + + @After + public void tearDown() throws Exception { + removeAll(); + } + + @Test + public void testGetAllWithPagination() throws Exception { + + final Tag tag_a = new Tag("tag-A", "#4561aa", "dusky"); + final Tag tag_b = new Tag("tag-B", "black", null); + final Tag tag_c = new Tag("tag-C", "navy", "kind of blue"); + + long userID = createUser("user", Role.USER, "user"); + SecurityContext sc = new MockSecurityContext(userService.get(userID)); + + tagService.insert(tag_a); + tagService.insert(tag_b); + tagService.insert(tag_c); + + TagList firstPage = restService.getAll(sc, 0, 2, null); + assertEquals(3, (long) firstPage.getCount()); + assertEquals(List.of(tag_a, tag_b), firstPage.getList()); + + TagList secondPage = restService.getAll(sc, 1, 2, null); + assertEquals(3, (long) firstPage.getCount()); + assertEquals(List.of(tag_c), secondPage.getList()); + } +}