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 private Long version;
55  
56    @Column(unique = true)
57    @NotNull
58    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
91    private AppContent contentRoot = new AppContent();
92  
93    @Type(value = io.hypersistence.utils.hibernate.type.json.JsonBinaryType.class)
94    @Column(columnDefinition = "jsonb")
95    @NotNull
96    private AppSettings settings = new AppSettings();
97  
98    @Type(value = io.hypersistence.utils.hibernate.type.json.JsonBinaryType.class)
99    @Column(columnDefinition = "jsonb")
100   @NotNull
101   private List<Component> components = new ArrayList<>();
102 
103   @Type(value = io.hypersistence.utils.hibernate.type.json.JsonBinaryType.class)
104   @Column(columnDefinition = "jsonb")
105   @NotNull
106   private AppStyling styling = new AppStyling();
107 
108   @Type(value = io.hypersistence.utils.hibernate.type.json.JsonBinaryType.class)
109   @Column(columnDefinition = "jsonb")
110   @NotNull
111   private List<AuthorizationRule> authorizationRules = new ArrayList<>();
112 
113   // <editor-fold desc="getters and setters">
114   public Long getId() {
115     return id;
116   }
117 
118   public Application setId(Long id) {
119     this.id = id;
120     return this;
121   }
122 
123   public Long getVersion() {
124     return version;
125   }
126 
127   public Application setVersion(Long version) {
128     this.version = version;
129     return this;
130   }
131 
132   public String getName() {
133     return name;
134   }
135 
136   public Application setName(String name) {
137     this.name = name;
138     return this;
139   }
140 
141   public String getTitle() {
142     return title;
143   }
144 
145   public Application setTitle(String title) {
146     this.title = title;
147     return this;
148   }
149 
150   public String getAdminComments() {
151     return adminComments;
152   }
153 
154   public Application setAdminComments(String adminComments) {
155     this.adminComments = adminComments;
156     return this;
157   }
158 
159   public String getPreviewText() {
160     return previewText;
161   }
162 
163   public Application setPreviewText(String previewText) {
164     this.previewText = previewText;
165     return this;
166   }
167 
168   public String getCrs() {
169     return crs;
170   }
171 
172   public Application setCrs(String crs) {
173     this.crs = crs;
174     return this;
175   }
176 
177   public Bounds getInitialExtent() {
178     return initialExtent;
179   }
180 
181   public Application setInitialExtent(Bounds initialExtent) {
182     this.initialExtent = initialExtent;
183     return this;
184   }
185 
186   public Bounds getMaxExtent() {
187     return maxExtent;
188   }
189 
190   public Application setMaxExtent(Bounds maxExtent) {
191     this.maxExtent = maxExtent;
192     return this;
193   }
194 
195   public AppContent getContentRoot() {
196     return contentRoot;
197   }
198 
199   public Application setContentRoot(AppContent contentRoot) {
200     this.contentRoot = contentRoot;
201     return this;
202   }
203 
204   public AppSettings getSettings() {
205     return settings;
206   }
207 
208   public Application setSettings(AppSettings layerSettings) {
209     this.settings = layerSettings;
210     return this;
211   }
212 
213   public List<Component> getComponents() {
214     return components;
215   }
216 
217   public Application setComponents(List<Component> components) {
218     this.components = components;
219     return this;
220   }
221 
222   public AppStyling getStyling() {
223     return styling;
224   }
225 
226   public Application setStyling(AppStyling styling) {
227     this.styling = styling;
228     return this;
229   }
230 
231   public List<AuthorizationRule> getAuthorizationRules() {
232     return authorizationRules;
233   }
234 
235   public Application setAuthorizationRules(List<AuthorizationRule> authorizationRules) {
236     this.authorizationRules = authorizationRules;
237     return this;
238   }
239 
240   // </editor-fold>
241 
242   @JsonIgnore
243   public Stream<AppTreeLayerNode> getAllAppTreeLayerNode() {
244     if (this.getContentRoot() == null) {
245       return Stream.empty();
246     }
247     Stream<AppTreeLayerNode> baseLayers = Stream.empty();
248     if (this.getContentRoot().getBaseLayerNodes() != null) {
249       baseLayers =
250           this.getContentRoot().getBaseLayerNodes().stream()
251               .filter(n -> "AppTreeLayerNode".equals(n.getObjectType()))
252               .map(n -> (AppTreeLayerNode) n);
253     }
254     Stream<AppTreeLayerNode> layers = Stream.empty();
255     if (this.getContentRoot().getLayerNodes() != null) {
256       layers =
257           this.getContentRoot().getLayerNodes().stream()
258               .filter(n -> "AppTreeLayerNode".equals(n.getObjectType()))
259               .map(n -> (AppTreeLayerNode) n);
260     }
261     return Stream.concat(baseLayers, layers);
262   }
263 
264   /**
265    * Return a GeoTools CoordinateReferenceSystem from this entities' CRS code or null if there is an
266    * error decoding it, which will be logged (only with stacktrace if loglevel is DEBUG).
267    *
268    * @return CoordinateReferenceSystem
269    */
270   @JsonIgnore
271   public org.geotools.api.referencing.crs.CoordinateReferenceSystem
272       getGeoToolsCoordinateReferenceSystem() {
273     org.geotools.api.referencing.crs.CoordinateReferenceSystem gtCrs = null;
274     try {
275       if (getCrs() != null) {
276         gtCrs = CRS.decode(getCrs());
277       }
278     } catch (Exception e) {
279       String message =
280           String.format(
281               "Application %d: error decoding CRS from code \"%s\": %s: %s",
282               getId(), getCrs(), e.getClass(), e.getMessage());
283       if (logger.isDebugEnabled()) {
284         logger.error(message, e);
285       } else {
286         logger.error(message);
287       }
288     }
289     return gtCrs;
290   }
291 
292   @JsonIgnore
293   public ViewerResponse getViewerResponse() {
294     return new ViewerResponse()
295         .kind(ViewerResponse.KindEnum.APP)
296         .name(getName())
297         .title(getTitle())
298         .styling(styling)
299         .components(components)
300         .i18nSettings(
301             requireNonNullElse(
302                 settings.getI18nSettings(), new AppI18nSettings().hideLanguageSwitcher(false)))
303         .uiSettings(
304             requireNonNullElse(
305                 settings.getUiSettings(), new AppUiSettings().hideLoginButton(false)))
306         .projections(List.of(getCrs()));
307   }
308 
309   @NotNull
310   public AppLayerSettings getAppLayerSettings(@NotNull AppTreeLayerNode node) {
311     return Optional.ofNullable(getSettings())
312         .map(AppSettings::getLayerSettings)
313         .map(layerSettingsMap -> layerSettingsMap.get(node.getId()))
314         .orElseGet(AppLayerSettings::new);
315   }
316 }