diff --git a/pom.xml b/pom.xml
index 457a53d6f..1d92be6f9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -97,6 +97,16 @@
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.22
+
+
+
+
diff --git a/shongo-client-cli/src/main/perl/Shongo/ClientCli/API/AuxiliaryData.pm b/shongo-client-cli/src/main/perl/Shongo/ClientCli/API/AuxiliaryData.pm
new file mode 100644
index 000000000..76a29a3b2
--- /dev/null
+++ b/shongo-client-cli/src/main/perl/Shongo/ClientCli/API/AuxiliaryData.pm
@@ -0,0 +1,45 @@
+#
+# Auxiliary data for ReservationRequestAbstract
+#
+# @author Filip Karnis
+#
+package Shongo::ClientCli::API::AuxiliaryData;
+use base qw(Shongo::ClientCli::API::Object);
+
+use strict;
+use warnings;
+
+use Shongo::Common;
+use Shongo::Console;
+
+#
+# Create a new instance of auxiliary data
+#
+# @static
+#
+sub new()
+{
+ my $class = shift;
+ my (%attributes) = @_;
+ my $self = Shongo::ClientCli::API::Object->new(@_);
+ bless $self, $class;
+
+ $self->set_object_class('AuxData');
+ $self->set_object_name('Auxiliary Data');
+ $self->add_attribute('tagName', {
+ 'required' => 1,
+ 'type' => 'string',
+ });
+ $self->add_attribute('enabled', {
+ 'required' => 1,
+ 'type' => 'bool',
+ });
+ $self->add_attribute('data', {
+ 'required' => 0,
+ 'type' => 'string',
+ });
+
+ return $self;
+}
+
+1;
diff --git a/shongo-client-cli/src/main/perl/Shongo/ClientCli/API/ReservationRequestAbstract.pm b/shongo-client-cli/src/main/perl/Shongo/ClientCli/API/ReservationRequestAbstract.pm
index 427f39823..013de7872 100644
--- a/shongo-client-cli/src/main/perl/Shongo/ClientCli/API/ReservationRequestAbstract.pm
+++ b/shongo-client-cli/src/main/perl/Shongo/ClientCli/API/ReservationRequestAbstract.pm
@@ -13,6 +13,7 @@ use Shongo::Common;
use Shongo::Console;
use Shongo::ClientCli::API::ReservationRequest;
use Shongo::ClientCli::API::ReservationRequestSet;
+use Shongo::ClientCli::API::AuxiliaryData;
# Enumeration of reservation request purpose
our $Purpose = ordered_hash(
@@ -89,6 +90,15 @@ sub new()
'OWNED' => 'Owned'
)
});
+ $self->add_attribute('auxData', {
+ 'type' => 'collection',
+ 'item' => {
+ 'title' => 'Auxiliary Data',
+ 'class' => 'Shongo::ClientCli::API::AuxiliaryData',
+ 'short' => 1,
+ },
+ 'optional' => 1,
+ });
return $self;
}
diff --git a/shongo-client-cli/src/main/perl/Shongo/ClientCli/ReservationService.pm b/shongo-client-cli/src/main/perl/Shongo/ClientCli/ReservationService.pm
index d3e17620b..1fa47c09d 100644
--- a/shongo-client-cli/src/main/perl/Shongo/ClientCli/ReservationService.pm
+++ b/shongo-client-cli/src/main/perl/Shongo/ClientCli/ReservationService.pm
@@ -274,7 +274,8 @@ sub list_reservation_requests()
{'field' => 'technology', 'title' => 'Technology'},
{'field' => 'allocationState', 'title' => 'Allocation'},
{'field' => 'executableState', 'title' => 'Executable'},
- {'field' => 'description', 'title' => 'Description'}
+ {'field' => 'description', 'title' => 'Description'},
+ {'field' => 'auxData', 'title' => 'Auxiliary Data'},
],
'data' => []
};
@@ -321,7 +322,8 @@ sub list_reservation_requests()
'technology' => $technologies,
'allocationState' => Shongo::ClientCli::API::ReservationRequest::format_state($reservation_request->{'allocationState'}),
'executableState' => Shongo::ClientCli::API::ReservationRequest::format_state($reservation_request->{'executableState'}),
- 'description' => $reservation_request->{'description'}
+ 'description' => $reservation_request->{'description'},
+ 'auxData' => $reservation_request->{'auxData'},
});
}
console_print_table($table);
diff --git a/shongo-client-cli/src/main/perl/Shongo/ClientCli/ResourceService.pm b/shongo-client-cli/src/main/perl/Shongo/ClientCli/ResourceService.pm
index 65f2efd25..5e8029439 100644
--- a/shongo-client-cli/src/main/perl/Shongo/ClientCli/ResourceService.pm
+++ b/shongo-client-cli/src/main/perl/Shongo/ClientCli/ResourceService.pm
@@ -15,6 +15,15 @@ use Shongo::ClientCli::API::Resource;
use Shongo::ClientCli::API::DeviceResource;
use Shongo::ClientCli::API::Alias;
+#
+# Tag types
+#
+our $TagType = ordered_hash(
+ 'DEFAULT' => 'Default',
+ 'NOTIFY_EMAIL' => 'Notify Email',
+ 'RESERVATION_DATA' => 'Reservation Data',
+);
+
#
# Populate shell by options for management of resources.
#
@@ -477,6 +486,19 @@ sub create_tag()
'title' => 'Tag name',
}
);
+ $tag->add_attribute(
+ 'type', {
+ 'required' => 1,
+ 'title' => 'Tag type',
+ 'type' => 'enum',
+ 'enum' => $Shongo::ClientCli::ResourceService::TagType,
+ }
+ );
+ $tag->add_attribute(
+ 'data', {
+ 'title' => 'Tag data',
+ }
+ );
my $id = $tag->create($attributes, $options);
if ( defined($id) ) {
@@ -514,13 +536,17 @@ sub list_tags()
'columns' => [
{'field' => 'id', 'title' => 'Identifier'},
{'field' => 'name', 'title' => 'Name'},
+ {'field' => 'type', 'title' => 'Type'},
+ {'field' => 'data', 'title' => 'Data'},
],
'data' => []
};
- foreach my $resource (@{$response}) {
+ foreach my $tag (@{$response}) {
push(@{$table->{'data'}}, {
- 'id' => $resource->{'id'},
- 'name' => $resource->{'name'},
+ 'id' => $tag->{'id'},
+ 'name' => $tag->{'name'},
+ 'type' => $tag->{'type'},
+ 'data' => $tag->{'data'},
});
}
console_print_table($table);
diff --git a/shongo-client-web/src/main/java/cz/cesnet/shongo/client/web/models/SpecificationType.java b/shongo-client-web/src/main/java/cz/cesnet/shongo/client/web/models/SpecificationType.java
index 4683796c4..f74309bf7 100644
--- a/shongo-client-web/src/main/java/cz/cesnet/shongo/client/web/models/SpecificationType.java
+++ b/shongo-client-web/src/main/java/cz/cesnet/shongo/client/web/models/SpecificationType.java
@@ -4,6 +4,10 @@
import cz.cesnet.shongo.client.web.ClientWebConfiguration;
import cz.cesnet.shongo.client.web.support.MessageProvider;
import cz.cesnet.shongo.controller.api.ReservationRequestSummary;
+import cz.cesnet.shongo.controller.api.Tag;
+
+import java.util.List;
+import java.util.stream.Collectors;
/**
* Type of specification for a reservation request.
@@ -102,15 +106,17 @@ public static SpecificationType fromReservationRequestSummary(ReservationRequest
case USED_ROOM:
return PERMANENT_ROOM_CAPACITY;
case RESOURCE:
- String resourceTags = reservationRequestSummary.getResourceTags();
+ List resourceTags = reservationRequestSummary.getResourceTags()
+ .stream()
+ .map(Tag::getName)
+ .collect(Collectors.toList());
String parkTagName = ClientWebConfiguration.getInstance().getParkingPlaceTagName();
String vehicleTagName = ClientWebConfiguration.getInstance().getVehicleTagName();
- if (resourceTags != null) {
- if (parkTagName != null && resourceTags.contains(parkTagName)) {
- return PARKING_PLACE;
- } else if (vehicleTagName != null && resourceTags.contains(vehicleTagName)) {
- return VEHICLE;
- }
+ if (parkTagName != null && resourceTags.contains(parkTagName)) {
+ return PARKING_PLACE;
+ }
+ else if (vehicleTagName != null && resourceTags.contains(vehicleTagName)) {
+ return VEHICLE;
}
return MEETING_ROOM;
default:
diff --git a/shongo-common/pom.xml b/shongo-common/pom.xml
index 8f5d00137..60713a072 100644
--- a/shongo-common/pom.xml
+++ b/shongo-common/pom.xml
@@ -53,6 +53,13 @@
+
+
+
+ io.hypersistence
+ hypersistence-utils-hibernate-52
+ 3.2.0
+
diff --git a/shongo-common/src/main/java/cz/cesnet/shongo/hibernate/package-info.java b/shongo-common/src/main/java/cz/cesnet/shongo/hibernate/package-info.java
index 645097e3b..67d3ab6bd 100644
--- a/shongo-common/src/main/java/cz/cesnet/shongo/hibernate/package-info.java
+++ b/shongo-common/src/main/java/cz/cesnet/shongo/hibernate/package-info.java
@@ -11,9 +11,11 @@
@TypeDef(name = PersistentLocalDate.NAME, typeClass = PersistentLocalDate.class),
@TypeDef(name = PersistentPeriod.NAME, typeClass = PersistentPeriod.class),
@TypeDef(name = PersistentInterval.NAME, typeClass = PersistentInterval.class),
- @TypeDef(name = PersistentReadablePartial.NAME, typeClass = PersistentReadablePartial.class)
+ @TypeDef(name = PersistentReadablePartial.NAME, typeClass = PersistentReadablePartial.class),
+ @TypeDef(name = "jsonb", typeClass = JsonBinaryType.class),
}) package cz.cesnet.shongo.hibernate;
+import io.hypersistence.utils.hibernate.type.json.JsonBinaryType;
import org.hibernate.annotations.TypeDef;
import org.hibernate.annotations.TypeDefs;
diff --git a/shongo-controller-api/pom.xml b/shongo-controller-api/pom.xml
index bce341e6b..0ebf28667 100644
--- a/shongo-controller-api/pom.xml
+++ b/shongo-controller-api/pom.xml
@@ -40,6 +40,11 @@
jackson-core
2.10.1
+
+ org.projectlombok
+ lombok
+ provided
+
diff --git a/shongo-controller-api/src/main/java/cz/cesnet/shongo/controller/api/AbstractReservationRequest.java b/shongo-controller-api/src/main/java/cz/cesnet/shongo/controller/api/AbstractReservationRequest.java
index 3ecbde003..48fe8d986 100644
--- a/shongo-controller-api/src/main/java/cz/cesnet/shongo/controller/api/AbstractReservationRequest.java
+++ b/shongo-controller-api/src/main/java/cz/cesnet/shongo/controller/api/AbstractReservationRequest.java
@@ -7,6 +7,9 @@
import cz.cesnet.shongo.controller.api.rpc.ReservationService;
import org.joda.time.DateTime;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Request for reservation of resources.
*
@@ -75,6 +78,11 @@ public abstract class AbstractReservationRequest extends IdentifiedComplexType
*/
private boolean isSchedulerDeleted = false;
+ /**
+ * Auxiliary data. This data are specified by the {@link Tag}s of {@link Resource} which is requested for reservation.
+ */
+ private List auxData = new ArrayList<>();
+
/**
* Constructor.
*/
@@ -291,6 +299,22 @@ public void setIsSchedulerDeleted(boolean isSchedulerDeleted)
this.isSchedulerDeleted = isSchedulerDeleted;
}
+ /**
+ * @return {@link #auxData}
+ */
+ public List getAuxData()
+ {
+ return auxData;
+ }
+
+ /**
+ * @param auxData sets the {@link #auxData}
+ */
+ public void setAuxData(List auxData)
+ {
+ this.auxData = auxData;
+ }
+
private static final String TYPE = "type";
private static final String DATETIME = "dateTime";
private static final String USER_ID = "userId";
@@ -303,6 +327,7 @@ public void setIsSchedulerDeleted(boolean isSchedulerDeleted)
private static final String REUSED_RESERVATION_REQUEST_MANDATORY = "reusedReservationRequestMandatory";
private static final String REUSEMENT = "reusement";
private static final String IS_SCHEDULER_DELETED = "isSchedulerDeleted";
+ public static final String AUX_DATA = "auxData";
@Override
public DataMap toData()
@@ -320,6 +345,7 @@ public DataMap toData()
dataMap.set(REUSED_RESERVATION_REQUEST_MANDATORY, reusedReservationRequestMandatory);
dataMap.set(REUSEMENT, reusement);
dataMap.set(IS_SCHEDULER_DELETED, isSchedulerDeleted);
+ dataMap.set(AUX_DATA, auxData);
return dataMap;
}
@@ -339,5 +365,6 @@ public void fromData(DataMap dataMap)
reusedReservationRequestMandatory = dataMap.getBool(REUSED_RESERVATION_REQUEST_MANDATORY);
reusement = dataMap.getEnum(REUSEMENT, ReservationRequestReusement.class);
isSchedulerDeleted = dataMap.getBool(IS_SCHEDULER_DELETED);
+ auxData = dataMap.getList(AUX_DATA, AuxiliaryData.class);
}
}
diff --git a/shongo-controller-api/src/main/java/cz/cesnet/shongo/controller/api/AuxiliaryData.java b/shongo-controller-api/src/main/java/cz/cesnet/shongo/controller/api/AuxiliaryData.java
new file mode 100644
index 000000000..d61a59e23
--- /dev/null
+++ b/shongo-controller-api/src/main/java/cz/cesnet/shongo/controller/api/AuxiliaryData.java
@@ -0,0 +1,99 @@
+package cz.cesnet.shongo.controller.api;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import cz.cesnet.shongo.api.AbstractComplexType;
+import cz.cesnet.shongo.api.DataMap;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = false)
+public class AuxiliaryData extends AbstractComplexType
+{
+
+ private static final ObjectMapper objectMapper = new ObjectMapper();
+
+ private String tagName;
+ private boolean enabled;
+ @ToString.Exclude
+ @EqualsAndHashCode.Exclude
+ private String data = objectMapper.nullNode().toString();
+
+ public AuxiliaryData(String tagName, boolean enabled, String data)
+ {
+ setTagName(tagName);
+ setEnabled(enabled);
+ setData(data);
+ }
+
+ public String getData()
+ {
+ return data;
+ }
+
+ @JsonIgnore
+ @ToString.Include(name = "data")
+ @EqualsAndHashCode.Include
+ public JsonNode getDataAsJsonNode()
+ {
+ if (data == null) {
+ return null;
+ }
+
+ try {
+ return objectMapper.readTree(data);
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void setData(String data)
+ {
+ if (data == null) {
+ this.data = objectMapper.nullNode().toString();
+ return;
+ }
+
+ this.data = data;
+ }
+
+ public void setData(JsonNode data)
+ {
+ try {
+ this.data = objectMapper.writeValueAsString(data);
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ @JsonIgnore
+ public String getClassName() {
+ return super.getClassName();
+ }
+
+ @Override
+ public DataMap toData()
+ {
+ DataMap dataMap = super.toData();
+ dataMap.set("tagName", tagName);
+ dataMap.set("enabled", enabled);
+ dataMap.set("data", data);
+ return dataMap;
+ }
+
+ @Override
+ public void fromData(DataMap dataMap)
+ {
+ super.fromData(dataMap);
+ tagName = dataMap.getString("tagName");
+ enabled = dataMap.getBoolean("enabled");
+ data = dataMap.getString("data");
+ }
+}
diff --git a/shongo-controller-api/src/main/java/cz/cesnet/shongo/controller/api/ReservationRequestSummary.java b/shongo-controller-api/src/main/java/cz/cesnet/shongo/controller/api/ReservationRequestSummary.java
index d72930c69..dc0be659a 100644
--- a/shongo-controller-api/src/main/java/cz/cesnet/shongo/controller/api/ReservationRequestSummary.java
+++ b/shongo-controller-api/src/main/java/cz/cesnet/shongo/controller/api/ReservationRequestSummary.java
@@ -110,7 +110,7 @@ public class ReservationRequestSummary extends IdentifiedComplexType
/**
* Resource tags.
*/
- private String resourceTags;
+ private List resourceTags = new ArrayList<>();
/**
* Specifies whether room has recording service.
@@ -127,20 +127,32 @@ public class ReservationRequestSummary extends IdentifiedComplexType
*/
private boolean allowCache = true;
+ /**
+ * Auxiliary data. This data are specified by the {@link Tag}s of {@link Resource} which is requested for reservation.
+ */
+ private String auxData;
+
/**
* @return {@link #resourceTags}
*/
- public String getResourceTags() {
+ public List getResourceTags() {
return resourceTags;
}
/**
* @param resourceTags sets the {@link #resourceTags}
*/
- public void setResourceTags(String resourceTags) {
+ public void setResourceTags(List resourceTags) {
this.resourceTags = resourceTags;
}
+ /**
+ * @param resourceTag adds tag to {@link #resourceTags}
+ */
+ public void addResourceTag(Tag resourceTag) {
+ this.resourceTags.add(resourceTag);
+ }
+
/**
* @return {@link #parentReservationRequestId}
*/
@@ -496,6 +508,22 @@ public void setAllowCache(boolean allowCache)
this.allowCache = allowCache;
}
+ /**
+ * @return {@link #auxData}
+ */
+ public String getAuxData()
+ {
+ return auxData;
+ }
+
+ /**
+ * @param auxData sets the {@link #auxData}
+ */
+ public void setAuxData(String auxData)
+ {
+ this.auxData = auxData;
+ }
+
private static final String PARENT_RESERVATION_REQUEST_ID = "parentReservationRequestId";
private static final String TYPE = "type";
private static final String DATETIME = "dateTime";
@@ -518,6 +546,7 @@ public void setAllowCache(boolean allowCache)
private static final String ROOM_HAS_RECORDINGS = "roomHasRecordings";
private static final String ALLOW_CACHE = "allowCache";
private static final String RESOURCE_TAGS = "resourceTags";
+ private static final String AUX_DATA = "auxData";
@Override
public DataMap toData()
@@ -545,6 +574,7 @@ public DataMap toData()
dataMap.set(ROOM_HAS_RECORDINGS, roomHasRecordings);
dataMap.set(ALLOW_CACHE, allowCache);
dataMap.set(RESOURCE_TAGS, resourceTags);
+ dataMap.set(AUX_DATA, auxData);
return dataMap;
}
@@ -573,7 +603,8 @@ public void fromData(DataMap dataMap)
roomHasRecordingService = dataMap.getBool(ROOM_HAS_RECORDING_SERVICE);
roomHasRecordings = dataMap.getBool(ROOM_HAS_RECORDINGS);
allowCache = dataMap.getBool(ALLOW_CACHE);
- resourceTags = dataMap.getString(RESOURCE_TAGS);
+ resourceTags = dataMap.getList(RESOURCE_TAGS, Tag.class);
+ auxData = dataMap.getString(AUX_DATA);
}
/**
diff --git a/shongo-controller-api/src/main/java/cz/cesnet/shongo/controller/api/Tag.java b/shongo-controller-api/src/main/java/cz/cesnet/shongo/controller/api/Tag.java
index d7284b6cd..364116229 100644
--- a/shongo-controller-api/src/main/java/cz/cesnet/shongo/controller/api/Tag.java
+++ b/shongo-controller-api/src/main/java/cz/cesnet/shongo/controller/api/Tag.java
@@ -3,6 +3,8 @@
import cz.cesnet.shongo.api.DataMap;
import cz.cesnet.shongo.api.IdentifiedComplexType;
+import java.util.Objects;
+
/**
*
* @author Ondřej Pavelka
@@ -10,6 +12,8 @@
public class Tag extends IdentifiedComplexType
{
String name;
+ TagType type = TagType.DEFAULT;
+ String data;
public String getName() {
return name;
@@ -19,13 +23,37 @@ public void setName(String name) {
this.name = name;
}
+ public TagType getType()
+ {
+ return type;
+ }
+
+ public void setType(TagType type)
+ {
+ this.type = type;
+ }
+
+ public String getData()
+ {
+ return data;
+ }
+
+ public void setData(String data)
+ {
+ this.data = data;
+ }
+
private static final String NAME = "name";
+ private static final String TYPE = "type";
+ private static final String DATA = "data";
@Override
public DataMap toData()
{
DataMap dataMap = super.toData();
dataMap.set(NAME,name);
+ dataMap.set(TYPE, type);
+ dataMap.set(DATA, data);
return dataMap;
}
@@ -34,6 +62,8 @@ public void fromData(DataMap dataMap)
{
super.fromData(dataMap);
name = dataMap.getString(NAME);
+ type = dataMap.getEnumRequired(TYPE, TagType.class);
+ data = dataMap.getString(DATA);
}
@Override
@@ -43,14 +73,12 @@ public boolean equals(Object o)
if (o == null || getClass() != o.getClass()) return false;
Tag tag = (Tag) o;
-
- return name.equals(tag.name);
-
+ return Objects.equals(name, tag.name) && type == tag.type && Objects.equals(data, tag.data);
}
@Override
public int hashCode()
{
- return name.hashCode();
+ return Objects.hash(name, type, data);
}
}
diff --git a/shongo-controller-api/src/main/java/cz/cesnet/shongo/controller/api/TagType.java b/shongo-controller-api/src/main/java/cz/cesnet/shongo/controller/api/TagType.java
new file mode 100644
index 000000000..03664d500
--- /dev/null
+++ b/shongo-controller-api/src/main/java/cz/cesnet/shongo/controller/api/TagType.java
@@ -0,0 +1,23 @@
+package cz.cesnet.shongo.controller.api;
+
+/**
+ * Type of {@link cz.cesnet.shongo.controller.booking.resource.Tag}.
+ */
+public enum TagType
+{
+ /**
+ * Simple tag. Does not do anything special.
+ */
+ DEFAULT,
+
+ /**
+ * Sends notifications to the email addresses specified in this {@link cz.cesnet.shongo.controller.booking.resource.Tag}.
+ */
+ NOTIFY_EMAIL,
+
+ /**
+ * Adds additional information specified in {@link cz.cesnet.shongo.controller.booking.resource.Tag}
+ * to {@link cz.cesnet.shongo.controller.booking.reservation.Reservation}.
+ */
+ RESERVATION_DATA,
+}
diff --git a/shongo-controller/pom.xml b/shongo-controller/pom.xml
index a3266ef6b..79cf72936 100644
--- a/shongo-controller/pom.xml
+++ b/shongo-controller/pom.xml
@@ -201,6 +201,13 @@
ical4j-zoneinfo-outlook
1.0.3
+
+
+
+ org.projectlombok
+ lombok
+ provided
+
diff --git a/shongo-controller/src/main/java/cz/cesnet/shongo/controller/api/rpc/ReservationServiceImpl.java b/shongo-controller/src/main/java/cz/cesnet/shongo/controller/api/rpc/ReservationServiceImpl.java
index a0b4cd879..2aa006225 100644
--- a/shongo-controller/src/main/java/cz/cesnet/shongo/controller/api/rpc/ReservationServiceImpl.java
+++ b/shongo-controller/src/main/java/cz/cesnet/shongo/controller/api/rpc/ReservationServiceImpl.java
@@ -8,6 +8,7 @@
import cz.cesnet.shongo.controller.api.*;
import cz.cesnet.shongo.controller.api.Reservation;
import cz.cesnet.shongo.controller.api.Specification;
+import cz.cesnet.shongo.controller.api.Tag;
import cz.cesnet.shongo.controller.api.request.*;
import cz.cesnet.shongo.controller.authorization.Authorization;
import cz.cesnet.shongo.controller.authorization.AuthorizationManager;
@@ -2041,7 +2042,21 @@ else if (type.equals("RESOURCE")) {
reservationRequestSummary.setAllowCache((Boolean) record[25]);
}
if (record[26] != null) {
- reservationRequestSummary.setResourceTags((String) record[26]);
+ String resourceTags = (String) record[26];
+ Arrays.stream(resourceTags.split("\\|")).map(String::trim).map(resourceTag -> {
+ String[] parts = resourceTag.split(",");
+ Tag tag = new Tag();
+ tag.setId(parts[0]);
+ tag.setName(parts[1]);
+ tag.setType(TagType.valueOf(parts[2]));
+ if (parts.length > 3) {
+ tag.setData(parts[3]);
+ }
+ return tag;
+ }).forEach(reservationRequestSummary::addResourceTag);
+ }
+ if (record[27] != null) {
+ reservationRequestSummary.setAuxData((String) record[27]);
}
return reservationRequestSummary;
}
diff --git a/shongo-controller/src/main/java/cz/cesnet/shongo/controller/booking/request/AbstractReservationRequest.java b/shongo-controller/src/main/java/cz/cesnet/shongo/controller/booking/request/AbstractReservationRequest.java
index a26ff68e1..630f7407f 100644
--- a/shongo-controller/src/main/java/cz/cesnet/shongo/controller/booking/request/AbstractReservationRequest.java
+++ b/shongo-controller/src/main/java/cz/cesnet/shongo/controller/booking/request/AbstractReservationRequest.java
@@ -1,5 +1,8 @@
package cz.cesnet.shongo.controller.booking.request;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
import cz.cesnet.shongo.CommonReportSet;
import cz.cesnet.shongo.PersistentObject;
import cz.cesnet.shongo.TodoImplementException;
@@ -9,9 +12,13 @@
import cz.cesnet.shongo.controller.ObjectType;
import cz.cesnet.shongo.controller.ReservationRequestPurpose;
import cz.cesnet.shongo.controller.ReservationRequestReusement;
+import cz.cesnet.shongo.controller.api.AuxiliaryData;
import cz.cesnet.shongo.controller.api.Controller;
import cz.cesnet.shongo.controller.booking.Allocation;
import cz.cesnet.shongo.controller.booking.ObjectIdentifier;
+import cz.cesnet.shongo.controller.booking.request.auxdata.AuxData;
+import cz.cesnet.shongo.controller.booking.resource.Resource;
+import cz.cesnet.shongo.controller.booking.resource.Tag;
import cz.cesnet.shongo.controller.booking.specification.Specification;
import cz.cesnet.shongo.controller.scheduler.Scheduler;
import cz.cesnet.shongo.controller.api.ReservationRequestType;
@@ -20,12 +27,15 @@
import cz.cesnet.shongo.report.Report;
import cz.cesnet.shongo.report.ReportableSimple;
import cz.cesnet.shongo.util.ObjectHelper;
+import org.hibernate.annotations.Type;
import org.joda.time.DateTime;
import org.joda.time.Period;
import javax.persistence.*;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
+import java.util.stream.Collectors;
/**
* Represents a base class for all reservation requests which contains common attributes.
@@ -37,6 +47,10 @@
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class AbstractReservationRequest extends PersistentObject implements ReportableSimple
{
+
+ @Transient
+ private final ObjectMapper objectMapper = new ObjectMapper();
+
/**
* Date/time when the {@link AbstractReservationRequest} was created.
*/
@@ -115,6 +129,11 @@ public abstract class AbstractReservationRequest extends PersistentObject implem
*/
private ReservationRequestReusement reusement;
+ /**
+ * Auxiliary data. This data are specified by the {@link Tag}s of {@link Resource} which is requested for reservation.
+ */
+ private String auxData;
+
@Id
@SequenceGenerator(name = "reservation_request_id", sequenceName = "reservation_request_id_seq", allocationSize = 1)
@GeneratedValue(strategy = GenerationType.AUTO, generator = "reservation_request_id")
@@ -385,6 +404,49 @@ public void setReusement(ReservationRequestReusement reusement)
this.reusement = reusement;
}
+ /**
+ * @return {@link #auxData}
+ */
+ @Type(type = "jsonb")
+ @Column(name = "aux_data", columnDefinition = "text")
+ public String getAuxData()
+ {
+ return auxData;
+ }
+
+ /**
+ * @return {@link #auxData}
+ */
+ @Transient
+ public List getAuxDataList() throws JsonProcessingException
+ {
+ if (auxData == null) {
+ return null;
+ }
+ return objectMapper.readValue(getAuxData(), new TypeReference<>(){});
+ }
+
+ /**
+ * @param auxData sets the {@link #auxData}
+ */
+ public void setAuxData(String auxData)
+ {
+ this.auxData = auxData;
+ }
+
+ /**
+ * @param auxDataApi sets the {@link #auxData}
+ */
+ public void setAuxData(List auxDataApi)
+ {
+ List auxData = auxDataApi.stream().map(AuxData::fromApi).collect(Collectors.toList());
+ try {
+ setAuxData(objectMapper.writeValueAsString(auxData));
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
/**
* Validate {@link AbstractReservationRequest}.
*
@@ -448,6 +510,7 @@ public boolean synchronizeFrom(AbstractReservationRequest reservationRequest, En
setReusedAllocation(reservationRequest.getReusedAllocation());
setReusedAllocationMandatory(reservationRequest.isReusedAllocationMandatory());
setReusement(reservationRequest.getReusement());
+ setAuxData(reservationRequest.getAuxData());
Specification oldSpecification = getSpecification();
Specification newSpecification = reservationRequest.getSpecification();
@@ -550,6 +613,12 @@ protected void toApi(cz.cesnet.shongo.controller.api.AbstractReservationRequest
ObjectIdentifier.formatId(reusedAllocation.getReservationRequest()), reusedAllocationMandatory);
}
api.setReusement(getReusement());
+ try {
+ api.setAuxData(getAuxDataList().stream().map(AuxData::toApi).collect(Collectors.toList()));
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ } catch (NullPointerException ignored) {
+ }
// Reservation request is deleted
if (state.equals(State.DELETED)) {
@@ -604,6 +673,7 @@ else if (getSpecification() != null && getSpecification().equalsId(specification
}
setReusedAllocationMandatory(api.isReusedReservationRequestMandatory());
setReusement(api.getReusement());
+ setAuxData(api.getAuxData());
}
/**
diff --git a/shongo-controller/src/main/java/cz/cesnet/shongo/controller/booking/request/AbstractReservationRequestAuxData.java b/shongo-controller/src/main/java/cz/cesnet/shongo/controller/booking/request/AbstractReservationRequestAuxData.java
new file mode 100644
index 000000000..92ebe267b
--- /dev/null
+++ b/shongo-controller/src/main/java/cz/cesnet/shongo/controller/booking/request/AbstractReservationRequestAuxData.java
@@ -0,0 +1,30 @@
+package cz.cesnet.shongo.controller.booking.request;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import cz.cesnet.shongo.controller.booking.specification.Specification;
+import lombok.Getter;
+import lombok.Setter;
+import org.hibernate.annotations.Immutable;
+import org.hibernate.annotations.Type;
+
+import javax.persistence.*;
+
+@Getter
+@Setter
+@Entity
+@Immutable
+@Table(name = "arr_aux_data")
+public class AbstractReservationRequestAuxData
+{
+
+ @Id
+ private Long id;
+
+ private String tagName;
+ private Boolean enabled;
+ @Type(type = "jsonb")
+ @Column(columnDefinition = "text")
+ private JsonNode data;
+ @ManyToOne(cascade = CascadeType.ALL, optional = false, fetch = FetchType.LAZY)
+ private Specification specification;
+}
diff --git a/shongo-controller/src/main/java/cz/cesnet/shongo/controller/booking/request/ReservationRequestManager.java b/shongo-controller/src/main/java/cz/cesnet/shongo/controller/booking/request/ReservationRequestManager.java
index 1b657cbac..3516db3b0 100644
--- a/shongo-controller/src/main/java/cz/cesnet/shongo/controller/booking/request/ReservationRequestManager.java
+++ b/shongo-controller/src/main/java/cz/cesnet/shongo/controller/booking/request/ReservationRequestManager.java
@@ -1,8 +1,10 @@
package cz.cesnet.shongo.controller.booking.request;
+import com.fasterxml.jackson.databind.JsonNode;
import cz.cesnet.shongo.AbstractManager;
import cz.cesnet.shongo.CommonReportSet;
import cz.cesnet.shongo.controller.ControllerReportSetHelper;
+import cz.cesnet.shongo.controller.api.TagType;
import cz.cesnet.shongo.controller.authorization.AuthorizationManager;
import cz.cesnet.shongo.controller.booking.Allocation;
import cz.cesnet.shongo.controller.booking.compartment.CompartmentSpecification;
@@ -10,6 +12,9 @@
import cz.cesnet.shongo.controller.booking.participant.InvitedPersonParticipant;
import cz.cesnet.shongo.controller.booking.participant.AbstractParticipant;
import cz.cesnet.shongo.controller.booking.participant.PersonParticipant;
+import cz.cesnet.shongo.controller.booking.request.auxdata.AuxDataFilter;
+import cz.cesnet.shongo.controller.booking.request.auxdata.AuxDataMerged;
+import cz.cesnet.shongo.controller.booking.request.auxdata.tagdata.TagData;
import cz.cesnet.shongo.controller.booking.specification.Specification;
import cz.cesnet.shongo.controller.booking.reservation.Reservation;
import cz.cesnet.shongo.controller.booking.reservation.ReservationManager;
@@ -19,7 +24,9 @@
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
+import javax.persistence.TypedQuery;
import java.util.*;
+import java.util.stream.Collectors;
/**
* Manager for {@link AbstractReservationRequest}.
@@ -635,4 +642,74 @@ public List detachReports(ReservationRequest reservationRequest
reservationRequest.clearReports();
return reports;
}
+
+ /**
+ * Creates {@link TagData} for given {@link AbstractReservationRequest} and its corresponding
+ * {@link cz.cesnet.shongo.controller.booking.resource.Tag}s.
+ *
+ * @param reservationRequest reservation request for which the {@link TagData} shall be created
+ * @param filter filter for data desired
+ * @return specific implementation of {@link TagData} based on {@link TagType}
+ * @param TagData implementation for corresponding {@link TagType}
+ */
+ public > List getTagData(AbstractReservationRequest reservationRequest, AuxDataFilter filter)
+ {
+ return getAuxData(reservationRequest, filter)
+ .stream()
+ .map(TagData::create)
+ .map(data -> (T) data)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Merge {@link cz.cesnet.shongo.controller.booking.request.auxdata.AuxData} from {@link AbstractReservationRequest}
+ * and data from its corresponding {@link cz.cesnet.shongo.controller.booking.resource.Tag}s.
+ *
+ * @param reservationRequest reservation request for which the data shall be merged
+ * @param filter filter for data desired
+ * @return merged data
+ */
+ private List getAuxData(AbstractReservationRequest reservationRequest, AuxDataFilter filter)
+ {
+ String queryString = "SELECT arr.tagName, rt.tag.type, arr.enabled, arr.data, rt.tag.data" +
+ " FROM AbstractReservationRequestAuxData arr" +
+ " JOIN ResourceSpecification res_spec ON res_spec.id = arr.specification.id" +
+ " JOIN ResourceTag rt ON rt.resource.id = res_spec.resource.id" +
+ " WHERE rt.tag.name = arr.tagName" +
+ " AND arr.id = :id";
+ if (filter.getTagName() != null) {
+ queryString += " AND rt.tag.name = :tagName";
+ }
+ if (filter.getTagType() != null) {
+ queryString += " AND rt.tag.type = :type";
+ }
+ if (filter.getEnabled() != null) {
+ queryString += " AND arr.enabled = :enabled";
+ }
+
+ TypedQuery