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