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