Joaquín Reñé
2025-10-07 146a0fb8b0e90f9196e569152f649baf60d6cc8f
securis/src/main/java/net/curisit/securis/services/ApplicationResource.java
....@@ -1,3 +1,6 @@
1
+/*
2
+ * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
3
+ */
14 package net.curisit.securis.services;
25
36 import java.util.Date;
....@@ -42,204 +45,246 @@
4245 import net.curisit.securis.utils.TokenHelper;
4346
4447 /**
45
- * Application resource, this service will provide methods to create, modify and
46
- * delete applications
47
- *
48
- * @author roberto <roberto.sanchez@curisit.net>
48
+ * ApplicationResource
49
+ * <p>
50
+ * REST endpoints to list, fetch, create, update and delete {@link Application}s.
51
+ * Security:
52
+ * <ul>
53
+ * <li>Listing filters by user's accessible application IDs unless ADMIN.</li>
54
+ * <li>Create/Modify/Delete restricted to ADMIN.</li>
55
+ * </ul>
56
+ * Side-effects:
57
+ * <ul>
58
+ * <li>Manages {@link ApplicationMetadata} lifecycle on create/update.</li>
59
+ * <li>Propagates metadata changes via {@link MetadataHelper}.</li>
60
+ * </ul>
61
+ *
62
+ * Author: roberto &lt;roberto.sanchez@curisit.net&gt;<br>
63
+ * Last reviewed by JRA on Oct 5, 2025.
4964 */
5065 @Path("/application")
5166 public class ApplicationResource {
5267
53
- @Inject
54
- TokenHelper tokenHelper;
68
+ @Inject TokenHelper tokenHelper;
69
+ @Inject MetadataHelper metadataHelper;
5570
56
- @Inject
57
- MetadataHelper metadataHelper;
71
+ @Context EntityManager em;
5872
59
- @Context
60
- EntityManager em;
73
+ private static final Logger LOG = LogManager.getLogger(ApplicationResource.class);
6174
62
- private static final Logger LOG = LogManager.getLogger(ApplicationResource.class);
75
+ /**
76
+ * ApplicationResource<p>
77
+ * Constructor
78
+ */
79
+ public ApplicationResource() {}
6380
64
- public ApplicationResource() {
65
- }
81
+ /**
82
+ * index<p>
83
+ * List applications visible to the current user.
84
+ *
85
+ * @param bsc security context
86
+ * @return 200 with list (possibly empty) or 200 empty if user has no app scope
87
+ */
88
+ @GET
89
+ @Path("/")
90
+ @Produces({ MediaType.APPLICATION_JSON })
91
+ @Securable
92
+ public Response index(@Context BasicSecurityContext bsc) {
93
+ LOG.info("Getting applications list ");
94
+ em.clear();
6695
67
- /**
68
- *
69
- * @return the server version in format majorVersion.minorVersion
70
- */
71
- @GET
72
- @Path("/")
73
- @Produces({ MediaType.APPLICATION_JSON })
74
- @Securable
75
- public Response index(@Context BasicSecurityContext bsc) {
76
- LOG.info("Getting applications list ");
96
+ TypedQuery<Application> q;
97
+ if (bsc.isUserInRole(BasicSecurityContext.ROL_ADMIN)) {
98
+ q = em.createNamedQuery("list-applications", Application.class);
99
+ } else {
100
+ if (bsc.getApplicationsIds() == null || bsc.getApplicationsIds().isEmpty()) {
101
+ return Response.ok().build();
102
+ }
103
+ q = em.createNamedQuery("list-applications-by_ids", Application.class);
104
+ q.setParameter("list_ids", bsc.getApplicationsIds());
105
+ }
106
+ List<Application> list = q.getResultList();
107
+ return Response.ok(list).build();
108
+ }
77109
78
- // EntityManager em = emProvider.get();
79
- em.clear();
110
+ /**
111
+ * get<p>
112
+ * Fetch a single application by ID.
113
+ *
114
+ * @param appid string ID
115
+ * @return 200 + entity or 404 if not found
116
+ * @throws SeCurisServiceException when ID is invalid or not found
117
+ */
118
+ @GET
119
+ @Path("/{appid}")
120
+ @Produces({ MediaType.APPLICATION_JSON })
121
+ @Securable
122
+ public Response get(@PathParam("appid") String appid) throws SeCurisServiceException {
123
+ LOG.info("Getting application data for id: {}: ", appid);
124
+ if (appid == null || "".equals(appid)) {
125
+ LOG.error("Application ID is mandatory");
126
+ return Response.status(Status.NOT_FOUND).build();
127
+ }
80128
81
- TypedQuery<Application> q;
82
- if (bsc.isUserInRole(BasicSecurityContext.ROL_ADMIN)) {
83
- q = em.createNamedQuery("list-applications", Application.class);
84
- } else {
85
- if (bsc.getApplicationsIds() == null || bsc.getApplicationsIds().isEmpty()) {
86
- return Response.ok().build();
87
- }
88
- q = em.createNamedQuery("list-applications-by_ids", Application.class);
129
+ em.clear();
130
+ Application app = null;
131
+ try {
132
+ LOG.info("READY to GET app: {}", appid);
133
+ app = em.find(Application.class, Integer.parseInt(appid));
134
+ } catch (Exception e) {
135
+ LOG.info("ERROR GETTING app: {}", e);
136
+ }
137
+ if (app == null) {
138
+ LOG.error("Application with id {} not found in DB", appid);
139
+ throw new SeCurisServiceException(ErrorCodes.NOT_FOUND, "Application not found with ID: " + appid);
140
+ }
141
+ return Response.ok(app).build();
142
+ }
89143
90
- q.setParameter("list_ids", bsc.getApplicationsIds());
91
- }
92
- List<Application> list = q.getResultList();
144
+ /**
145
+ * create<p>
146
+ * Create a new application with optional metadata entries.
147
+ *
148
+ * @param app application payload
149
+ * @param token auth token (audited externally)
150
+ * @return 200 + persisted entity
151
+ */
152
+ @POST
153
+ @Path("/")
154
+ @Consumes(MediaType.APPLICATION_JSON)
155
+ @Produces({ MediaType.APPLICATION_JSON })
156
+ @EnsureTransaction
157
+ @Securable(roles = Rol.ADMIN)
158
+ @RolesAllowed(BasicSecurityContext.ROL_ADMIN)
159
+ public Response create(Application app, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) {
160
+ LOG.info("Creating new application");
161
+ app.setCreationTimestamp(new Date());
162
+ em.persist(app);
93163
94
- return Response.ok(list).build();
95
- }
164
+ if (app.getApplicationMetadata() != null) {
165
+ for (ApplicationMetadata md : app.getApplicationMetadata()) {
166
+ md.setApplication(app);
167
+ md.setCreationTimestamp(new Date());
168
+ em.persist(md);
169
+ }
170
+ }
171
+ LOG.info("Creating application ({}) with date: {}", app.getId(), app.getCreationTimestamp());
172
+ return Response.ok(app).build();
173
+ }
96174
97
- /**
98
- *
99
- * @return the server version in format majorVersion.minorVersion
100
- * @throws SeCurisServiceException
101
- */
102
- @GET
103
- @Path("/{appid}")
104
- @Produces({ MediaType.APPLICATION_JSON })
105
- @Securable
106
- public Response get(@PathParam("appid") String appid) throws SeCurisServiceException {
107
- LOG.info("Getting application data for id: {}: ", appid);
108
- if (appid == null || "".equals(appid)) {
109
- LOG.error("Application ID is mandatory");
110
- return Response.status(Status.NOT_FOUND).build();
111
- }
175
+ /**
176
+ * modify<p>
177
+ * Update core fields and reconcile metadata set:
178
+ * <ul>
179
+ * <li>Removes missing keys, merges existing, persists new.</li>
180
+ * <li>Propagates metadata if there were changes.</li>
181
+ * </ul>
182
+ *
183
+ * @param appid path ID
184
+ * @param app new state
185
+ */
186
+ @PUT
187
+ @POST
188
+ @Path("/{appid}")
189
+ @EnsureTransaction
190
+ @Consumes(MediaType.APPLICATION_JSON)
191
+ @Produces({ MediaType.APPLICATION_JSON })
192
+ @Securable(roles = Rol.ADMIN)
193
+ @RolesAllowed(BasicSecurityContext.ROL_ADMIN)
194
+ public Response modify(Application app, @PathParam("appid") String appid, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) {
195
+ LOG.info("Modifying application with id: {}", appid);
196
+ Application currentapp = em.find(Application.class, Integer.parseInt(appid));
197
+ if (currentapp == null) {
198
+ LOG.error("Application with id {} not found in DB", appid);
199
+ return Response.status(Status.NOT_FOUND)
200
+ .header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, "Application not found with ID: " + appid)
201
+ .build();
202
+ }
112203
113
- em.clear();
204
+ currentapp.setCode(app.getCode());
205
+ currentapp.setName(app.getName());
206
+ currentapp.setLicenseFilename(app.getLicenseFilename());
207
+ currentapp.setDescription(app.getDescription());
114208
115
- Application app = null;
116
- try {
117
- LOG.info("READY to GET app: {}", appid);
118
- app = em.find(Application.class, Integer.parseInt(appid));
119
- } catch (Exception e) {
120
- LOG.info("ERROR GETTING app: {}", e);
121
- }
122
- if (app == null) {
123
- LOG.error("Application with id {} not found in DB", appid);
124
- throw new SeCurisServiceException(ErrorCodes.NOT_FOUND, "Application not found with ID: " + appid);
125
- }
209
+ Set<ApplicationMetadata> newMD = app.getApplicationMetadata();
210
+ Set<ApplicationMetadata> oldMD = currentapp.getApplicationMetadata();
211
+ boolean metadataChanges = !metadataHelper.match(newMD, oldMD);
212
+ if (metadataChanges) {
213
+ Map<String, ApplicationMetadata> directOldMD = getMapMD(oldMD);
214
+ Map<String, ApplicationMetadata> directNewMD = getMapMD(newMD);
126215
127
- return Response.ok(app).build();
128
- }
216
+ // Remove deleted MD
217
+ for (ApplicationMetadata currentMd : oldMD) {
218
+ if (newMD == null || !directNewMD.containsKey(currentMd.getKey())) {
219
+ em.remove(currentMd);
220
+ }
221
+ }
222
+ // Merge or persist
223
+ if (newMD != null) {
224
+ for (ApplicationMetadata md : newMD) {
225
+ if (directOldMD.containsKey(md.getKey())) {
226
+ em.merge(md);
227
+ } else {
228
+ md.setApplication(currentapp);
229
+ if (md.getCreationTimestamp() == null) {
230
+ md.setCreationTimestamp(app.getCreationTimestamp());
231
+ }
232
+ em.persist(md);
233
+ }
234
+ }
235
+ }
236
+ currentapp.setApplicationMetadata(app.getApplicationMetadata());
237
+ }
129238
130
- @POST
131
- @Path("/")
132
- @Consumes(MediaType.APPLICATION_JSON)
133
- @Produces({ MediaType.APPLICATION_JSON })
134
- @EnsureTransaction
135
- @Securable(roles = Rol.ADMIN)
136
- @RolesAllowed(BasicSecurityContext.ROL_ADMIN)
137
- public Response create(Application app, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) {
138
- LOG.info("Creating new application");
139
- // EntityManager em = emProvider.get();
140
- app.setCreationTimestamp(new Date());
141
- em.persist(app);
239
+ em.merge(currentapp);
240
+ if (metadataChanges) {
241
+ metadataHelper.propagateMetadata(em, currentapp);
242
+ }
243
+ return Response.ok(currentapp).build();
244
+ }
142245
143
- if (app.getApplicationMetadata() != null) {
144
- for (ApplicationMetadata md : app.getApplicationMetadata()) {
145
- md.setApplication(app);
146
- md.setCreationTimestamp(new Date());
147
- em.persist(md);
148
- }
149
- }
150
- LOG.info("Creating application ({}) with date: {}", app.getId(), app.getCreationTimestamp());
246
+ /**
247
+ * getMapMD<p>
248
+ * Build a map from metadata key → entity for fast reconciliation.
249
+ *
250
+ * @param applicationMetadata
251
+ * @return mapMD
252
+ */
253
+ private Map<String, ApplicationMetadata> getMapMD(Set<ApplicationMetadata> amd) {
254
+ Map<String, ApplicationMetadata> map = new HashMap<>();
255
+ if (amd != null) {
256
+ for (ApplicationMetadata m : amd) {
257
+ map.put(m.getKey(), m);
258
+ }
259
+ }
260
+ return map;
261
+ }
151262
152
- return Response.ok(app).build();
153
- }
154
-
155
- @PUT
156
- @POST
157
- @Path("/{appid}")
158
- @EnsureTransaction
159
- @Consumes(MediaType.APPLICATION_JSON)
160
- @Produces({ MediaType.APPLICATION_JSON })
161
- @Securable(roles = Rol.ADMIN)
162
- @RolesAllowed(BasicSecurityContext.ROL_ADMIN)
163
- public Response modify(Application app, @PathParam("appid") String appid, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) {
164
- LOG.info("Modifying application with id: {}", appid);
165
- // EntityManager em = emProvider.get();
166
- Application currentapp = em.find(Application.class, Integer.parseInt(appid));
167
- if (currentapp == null) {
168
- LOG.error("Application with id {} not found in DB", appid);
169
- return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, "Application not found with ID: " + appid).build();
170
- }
171
- currentapp.setCode(app.getCode());
172
- currentapp.setName(app.getName());
173
- currentapp.setLicenseFilename(app.getLicenseFilename());
174
- currentapp.setDescription(app.getDescription());
175
-
176
- Set<ApplicationMetadata> newMD = app.getApplicationMetadata();
177
- Set<ApplicationMetadata> oldMD = currentapp.getApplicationMetadata();
178
- boolean metadataChanges = !metadataHelper.match(newMD, oldMD);
179
- if (metadataChanges) {
180
- Map<String, ApplicationMetadata> directOldMD = getMapMD(oldMD);
181
- Map<String, ApplicationMetadata> directNewMD = getMapMD(newMD);
182
- for (ApplicationMetadata currentMd : oldMD) {
183
- if (newMD == null || !directNewMD.containsKey(currentMd.getKey())) {
184
- em.remove(currentMd);
185
- }
186
- }
187
-
188
- if (newMD != null) {
189
- for (ApplicationMetadata md : newMD) {
190
- if (directOldMD.containsKey(md.getKey())) {
191
- em.merge(md);
192
- } else {
193
- md.setApplication(currentapp);
194
- if (md.getCreationTimestamp() == null) {
195
- md.setCreationTimestamp(app.getCreationTimestamp());
196
- }
197
- em.persist(md);
198
- }
199
- }
200
- }
201
- currentapp.setApplicationMetadata(app.getApplicationMetadata());
202
- }
203
- em.merge(currentapp);
204
- if (metadataChanges) {
205
- metadataHelper.propagateMetadata(em, currentapp);
206
- }
207
- return Response.ok(currentapp).build();
208
- }
209
-
210
- private Map<String, ApplicationMetadata> getMapMD(Set<ApplicationMetadata> amd) {
211
- Map<String, ApplicationMetadata> map = new HashMap<String, ApplicationMetadata>();
212
- if (amd != null) {
213
- for (ApplicationMetadata applicationMetadata : amd) {
214
- map.put(applicationMetadata.getKey(), applicationMetadata);
215
- }
216
- }
217
- return map;
218
- }
219
-
220
- @DELETE
221
- @Path("/{appid}")
222
- @EnsureTransaction
223
- @Produces({ MediaType.APPLICATION_JSON })
224
- @Securable(roles = Rol.ADMIN)
225
- @RolesAllowed(BasicSecurityContext.ROL_ADMIN)
226
- public Response delete(@PathParam("appid") String appid, @Context HttpServletRequest request) {
227
- LOG.info("Deleting app with id: {}", appid);
228
- // EntityManager em = emProvider.get();
229
- Application app = em.find(Application.class, Integer.parseInt(appid));
230
- if (app == null) {
231
- LOG.error("Application with id {} can not be deleted, It was not found in DB", appid);
232
- return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, "Application not found with ID: " + appid).build();
233
- }
234
- /*
235
- * if (app.getLicenseTypes() != null &&
236
- * !app.getLicenseTypes().isEmpty()) { throw new
237
- * SeCurisServiceException(ErrorCodes.NOT_FOUND,
238
- * "Application can not be deleted becasue has assigned one or more License types, ID: "
239
- * + appid); }
240
- */
241
- em.remove(app);
242
- return Response.ok(Utils.createMap("success", true, "id", appid)).build();
243
- }
244
-
263
+ /**
264
+ * delete<p>
265
+ * Delete an application by ID.
266
+ * <p>Note: deletion is not allowed if there are dependent entities (enforced by DB/cascade).</p>
267
+ *
268
+ * @param appId
269
+ * @param request
270
+ */
271
+ @DELETE
272
+ @Path("/{appid}")
273
+ @EnsureTransaction
274
+ @Produces({ MediaType.APPLICATION_JSON })
275
+ @Securable(roles = Rol.ADMIN)
276
+ @RolesAllowed(BasicSecurityContext.ROL_ADMIN)
277
+ public Response delete(@PathParam("appid") String appid, @Context HttpServletRequest request) {
278
+ LOG.info("Deleting app with id: {}", appid);
279
+ Application app = em.find(Application.class, Integer.parseInt(appid));
280
+ if (app == null) {
281
+ LOG.error("Application with id {} can not be deleted, It was not found in DB", appid);
282
+ return Response.status(Status.NOT_FOUND)
283
+ .header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, "Application not found with ID: " + appid)
284
+ .build();
285
+ }
286
+ em.remove(app);
287
+ return Response.ok(Utils.createMap("success", true, "id", appid)).build();
288
+ }
245289 }
290
+