View Javadoc
1   /*
2    * Copyright (C) 2023 B3Partners B.V.
3    *
4    * SPDX-License-Identifier: MIT
5    */
6   package org.tailormap.api.persistence;
7   
8   import com.fasterxml.jackson.annotation.JsonIgnore;
9   import jakarta.persistence.AttributeOverride;
10  import jakarta.persistence.AttributeOverrides;
11  import jakarta.persistence.Column;
12  import jakarta.persistence.Embedded;
13  import jakarta.persistence.Entity;
14  import jakarta.persistence.EntityListeners;
15  import jakarta.persistence.GeneratedValue;
16  import jakarta.persistence.GenerationType;
17  import jakarta.persistence.Id;
18  import jakarta.persistence.Version;
19  import jakarta.validation.constraints.NotNull;
20  import java.lang.invoke.MethodHandles;
21  import java.util.ArrayList;
22  import java.util.List;
23  import java.util.Optional;
24  import java.util.stream.Stream;
25  import org.geotools.referencing.CRS;
26  import org.hibernate.annotations.Type;
27  import org.slf4j.Logger;
28  import org.slf4j.LoggerFactory;
29  import org.tailormap.api.persistence.json.AppContent;
30  import org.tailormap.api.persistence.json.AppLayerSettings;
31  import org.tailormap.api.persistence.json.AppSettings;
32  import org.tailormap.api.persistence.json.AppTreeLayerNode;
33  import org.tailormap.api.persistence.json.AuthorizationRule;
34  import org.tailormap.api.persistence.json.Bounds;
35  import org.tailormap.api.persistence.listener.EntityEventPublisher;
36  import org.tailormap.api.viewer.model.AppStyling;
37  import org.tailormap.api.viewer.model.Component;
38  
39  @Entity
40  @EntityListeners(EntityEventPublisher.class)
41  public class Application {
42    private static final Logger logger =
43        LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
44  
45    @Id
46    @GeneratedValue(strategy = GenerationType.IDENTITY)
47    private Long id;
48  
49    @Version
50    private Long version;
51  
52    @Column(unique = true)
53    @NotNull private String name;
54  
55    private String title;
56  
57    @Column(columnDefinition = "text")
58    private String adminComments;
59  
60    @Column(columnDefinition = "text")
61    private String previewText;
62  
63    @NotNull private String crs;
64  
65    @Embedded
66    @AttributeOverrides({
67      @AttributeOverride(name = "minx", column = @Column(name = "initial_minx")),
68      @AttributeOverride(name = "maxx", column = @Column(name = "initial_maxx")),
69      @AttributeOverride(name = "miny", column = @Column(name = "initial_miny")),
70      @AttributeOverride(name = "maxy", column = @Column(name = "initial_maxy"))
71    })
72    private Bounds initialExtent;
73  
74    @Embedded
75    @AttributeOverrides({
76      @AttributeOverride(name = "minx", column = @Column(name = "max_minx")),
77      @AttributeOverride(name = "maxx", column = @Column(name = "max_maxx")),
78      @AttributeOverride(name = "miny", column = @Column(name = "max_miny")),
79      @AttributeOverride(name = "maxy", column = @Column(name = "max_maxy"))
80    })
81    private Bounds maxExtent;
82  
83    @Type(value = io.hypersistence.utils.hibernate.type.json.JsonBinaryType.class)
84    @Column(columnDefinition = "jsonb")
85    @NotNull private AppContent contentRoot = new AppContent();
86  
87    @JsonIgnore
88    private transient AppContent oldContentRoot;
89  
90    @Type(value = io.hypersistence.utils.hibernate.type.json.JsonBinaryType.class)
91    @Column(columnDefinition = "jsonb")
92    @NotNull private AppSettings settings = new AppSettings();
93  
94    @Type(value = io.hypersistence.utils.hibernate.type.json.JsonBinaryType.class)
95    @Column(columnDefinition = "jsonb")
96    @NotNull private List<Component> components = new ArrayList<>();
97  
98    @Type(value = io.hypersistence.utils.hibernate.type.json.JsonBinaryType.class)
99    @Column(columnDefinition = "jsonb")
100   @NotNull private AppStyling styling = new AppStyling();
101 
102   @Type(value = io.hypersistence.utils.hibernate.type.json.JsonBinaryType.class)
103   @Column(columnDefinition = "jsonb")
104   @NotNull private List<AuthorizationRule> authorizationRules = new ArrayList<>();
105 
106   // <editor-fold desc="getters and setters">
107   public Long getId() {
108     return id;
109   }
110 
111   public Application setId(Long id) {
112     this.id = id;
113     return this;
114   }
115 
116   public Long getVersion() {
117     return version;
118   }
119 
120   public Application setVersion(Long version) {
121     this.version = version;
122     return this;
123   }
124 
125   public String getName() {
126     return name;
127   }
128 
129   public Application setName(String name) {
130     this.name = name;
131     return this;
132   }
133 
134   public String getTitle() {
135     return title;
136   }
137 
138   public Application setTitle(String title) {
139     this.title = title;
140     return this;
141   }
142 
143   public String getAdminComments() {
144     return adminComments;
145   }
146 
147   public Application setAdminComments(String adminComments) {
148     this.adminComments = adminComments;
149     return this;
150   }
151 
152   public String getPreviewText() {
153     return previewText;
154   }
155 
156   public Application setPreviewText(String previewText) {
157     this.previewText = previewText;
158     return this;
159   }
160 
161   public String getCrs() {
162     return crs;
163   }
164 
165   public Application setCrs(String crs) {
166     this.crs = crs;
167     return this;
168   }
169 
170   public Bounds getInitialExtent() {
171     return initialExtent;
172   }
173 
174   public Application setInitialExtent(Bounds initialExtent) {
175     this.initialExtent = initialExtent;
176     return this;
177   }
178 
179   public Bounds getMaxExtent() {
180     return maxExtent;
181   }
182 
183   public Application setMaxExtent(Bounds maxExtent) {
184     this.maxExtent = maxExtent;
185     return this;
186   }
187 
188   public AppContent getContentRoot() {
189     return contentRoot;
190   }
191 
192   public Application setContentRoot(AppContent contentRoot) {
193     this.oldContentRoot = this.contentRoot;
194     this.contentRoot = contentRoot;
195     return this;
196   }
197   /**
198    * This method is used to get the old content root before it is updated. It is used in the ApplicationEventHandler
199    * to compare the old and new content roots, more specifically to get the old AppTreeLayerNode nodes.
200    *
201    * @see #getAllOldAppTreeLayerNode()
202    */
203   public AppContent getOldContentRoot() {
204     return this.oldContentRoot;
205   }
206 
207   public AppSettings getSettings() {
208     return settings;
209   }
210 
211   public Application setSettings(AppSettings layerSettings) {
212     this.settings = layerSettings;
213     return this;
214   }
215 
216   public List<Component> getComponents() {
217     return components;
218   }
219 
220   public Application setComponents(List<Component> components) {
221     this.components = components;
222     return this;
223   }
224 
225   public AppStyling getStyling() {
226     return styling;
227   }
228 
229   public Application setStyling(AppStyling styling) {
230     this.styling = styling;
231     return this;
232   }
233 
234   public List<AuthorizationRule> getAuthorizationRules() {
235     return authorizationRules;
236   }
237 
238   public Application setAuthorizationRules(List<AuthorizationRule> authorizationRules) {
239     this.authorizationRules = authorizationRules;
240     return this;
241   }
242 
243   // </editor-fold>
244   @JsonIgnore
245   public Stream<AppTreeLayerNode> getAllOldAppTreeLayerNode() {
246     return this.getAppTreeLayerNodeStream(this.getOldContentRoot());
247   }
248 
249   @JsonIgnore
250   public Stream<AppTreeLayerNode> getAllAppTreeLayerNode() {
251     return this.getAppTreeLayerNodeStream(this.getContentRoot());
252   }
253 
254   @JsonIgnore
255   private Stream<AppTreeLayerNode> getAppTreeLayerNodeStream(AppContent contentRoot) {
256     if (contentRoot == null) {
257       return Stream.empty();
258     }
259     Stream<AppTreeLayerNode> baseLayers = Stream.empty();
260     if (contentRoot.getBaseLayerNodes() != null) {
261       baseLayers = contentRoot.getBaseLayerNodes().stream()
262           .filter(n -> "AppTreeLayerNode".equals(n.getObjectType()))
263           .map(n -> (AppTreeLayerNode) n);
264     }
265     Stream<AppTreeLayerNode> layers = Stream.empty();
266     if (contentRoot.getLayerNodes() != null) {
267       layers = contentRoot.getLayerNodes().stream()
268           .filter(n -> "AppTreeLayerNode".equals(n.getObjectType()))
269           .map(n -> (AppTreeLayerNode) n);
270     }
271     return Stream.concat(baseLayers, layers);
272   }
273 
274   /**
275    * Return a GeoTools CoordinateReferenceSystem from this entities' CRS code or null if there is an error decoding
276    * it, which will be logged (only with stacktrace if loglevel is DEBUG).
277    *
278    * @return CoordinateReferenceSystem
279    */
280   @JsonIgnore
281   public org.geotools.api.referencing.crs.CoordinateReferenceSystem getGeoToolsCoordinateReferenceSystem() {
282     org.geotools.api.referencing.crs.CoordinateReferenceSystem gtCrs = null;
283     try {
284       if (getCrs() != null) {
285         gtCrs = CRS.decode(getCrs());
286       }
287     } catch (Exception e) {
288       String message = String.format(
289           "Application %d: error decoding CRS from code \"%s\": %s: %s",
290           getId(), getCrs(), e.getClass(), e.getMessage());
291       if (logger.isDebugEnabled()) {
292         logger.error(message, e);
293       } else {
294         logger.error(message);
295       }
296     }
297     return gtCrs;
298   }
299 
300   @NotNull public AppLayerSettings getAppLayerSettings(@NotNull AppTreeLayerNode node) {
301     return Optional.ofNullable(getSettings())
302         .map(AppSettings::getLayerSettings)
303         .map(layerSettingsMap -> layerSettingsMap.get(node.getId()))
304         .orElseGet(AppLayerSettings::new);
305   }
306 }