1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.onehippo.forge.content.exim.repository.jaxrs;
17
18 import java.io.File;
19 import java.io.FileOutputStream;
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.io.OutputStream;
23 import java.io.PrintStream;
24 import java.security.Principal;
25 import java.util.Arrays;
26 import java.util.Base64;
27 import java.util.Collection;
28 import java.util.LinkedHashSet;
29 import java.util.List;
30 import java.util.Set;
31
32 import javax.jcr.Credentials;
33 import javax.jcr.LoginException;
34 import javax.jcr.Node;
35 import javax.jcr.NodeIterator;
36 import javax.jcr.RepositoryException;
37 import javax.jcr.Session;
38 import javax.jcr.SimpleCredentials;
39 import javax.jcr.query.Query;
40 import javax.jcr.query.QueryResult;
41 import jakarta.servlet.http.HttpServletRequest;
42 import jakarta.ws.rs.core.SecurityContext;
43
44 import org.apache.commons.collections4.CollectionUtils;
45 import org.apache.commons.io.IOUtils;
46 import org.apache.commons.lang3.StringUtils;
47 import org.apache.commons.lang3.math.NumberUtils;
48 import org.apache.commons.lang3.time.FastDateFormat;
49 import org.apache.commons.vfs2.FileObject;
50 import org.apache.cxf.jaxrs.ext.multipart.Attachment;
51 import org.apache.cxf.jaxrs.ext.multipart.ContentDisposition;
52 import org.onehippo.cms7.utilities.logging.PrintStreamLogger;
53 import org.onehippo.forge.content.exim.core.ContentMigrationRecord;
54 import org.onehippo.forge.content.exim.core.util.AntPathMatcher;
55 import org.onehippo.forge.content.exim.core.util.TeeLoggerWrapper;
56 import org.onehippo.forge.content.exim.repository.jaxrs.param.ExecutionParams;
57 import org.onehippo.forge.content.exim.repository.jaxrs.param.QueriesAndPaths;
58 import org.onehippo.forge.content.exim.repository.jaxrs.param.ResultItem;
59 import org.onehippo.forge.content.exim.repository.jaxrs.status.ProcessStatus;
60 import org.onehippo.forge.content.exim.repository.jaxrs.util.ServletRequestUtils;
61 import org.onehippo.forge.content.pojo.model.ContentNode;
62 import org.slf4j.Logger;
63 import org.slf4j.LoggerFactory;
64
65 import com.fasterxml.jackson.core.JsonParser;
66 import com.fasterxml.jackson.core.JsonProcessingException;
67 import com.fasterxml.jackson.databind.ObjectMapper;
68 import com.fasterxml.jackson.databind.SerializationFeature;
69
70
71
72
73 public abstract class AbstractContentEximService {
74
75 private static Logger log = LoggerFactory.getLogger(AbstractContentEximService.class);
76
77
78
79
80 protected static final Credentials SYSTEM_CREDENTIALS = new SimpleCredentials("system", new char[] {});
81
82
83
84
85 protected static final String TEMP_PREFIX = "_exim_";
86
87
88
89
90 protected static final String EXIM_EXECUTION_LOG_REL_PATH = "EXIM-INF/execution.log";
91
92
93
94
95 protected static final String EXIM_SUMMARY_BINARIES_LOG_REL_PATH = "EXIM-INF/summary-binaries.log";
96
97
98
99
100 protected static final String EXIM_SUMMARY_DOCUMENTS_LOG_REL_PATH = "EXIM-INF/summary-documents.log";
101
102
103
104
105 protected static final String BINARY_ATTACHMENT_REL_PATH = "EXIM-INF/data/attachments";
106
107
108
109
110
111 protected static final String STOP_REQUEST_FILE_REL_PATH = "EXIM-INF/_stop_";
112
113 private ProcessMonitor processMonitor;
114
115
116
117
118 private ObjectMapper objectMapper;
119
120
121
122
123 private Session daemonSession;
124
125
126
127
128 public AbstractContentEximService() {
129 objectMapper = new ObjectMapper();
130 objectMapper.configure(JsonParser.Feature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER, true);
131 objectMapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
132 objectMapper.configure(JsonParser.Feature.ALLOW_MISSING_VALUES, true);
133 objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
134 objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
135 objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
136 }
137
138 protected void setProcessMonitor(ProcessMonitor processMonitor) {
139 this.processMonitor = processMonitor;
140 }
141
142 protected ProcessMonitor getProcessMonitor() {
143 return processMonitor;
144 }
145
146
147
148
149
150 protected ObjectMapper getObjectMapper() {
151 return objectMapper;
152 }
153
154
155
156
157
158
159
160 protected Session getDaemonSession() {
161 return daemonSession;
162 }
163
164
165
166
167
168 protected void setDaemonSession(Session daemonSession) {
169 this.daemonSession = daemonSession;
170 }
171
172
173
174
175
176
177
178 protected Session createSession() throws LoginException, RepositoryException {
179 return getDaemonSession().impersonate(SYSTEM_CREDENTIALS);
180 }
181
182
183
184
185
186
187 protected boolean isStopRequested(FileObject baseFolder) {
188 try {
189 FileObject stopSignalFile = baseFolder.resolveFile(STOP_REQUEST_FILE_REL_PATH);
190 return stopSignalFile.exists();
191 } catch (Exception e) {
192 log.error("Failed to check stop request file.", e);
193 }
194
195 return false;
196 }
197
198
199
200
201
202
203
204 protected String toJsonString(Object object) throws JsonProcessingException {
205 return getObjectMapper().writeValueAsString(object);
206 }
207
208
209
210
211
212
213
214
215 protected String attachmentToString(Attachment attachment, String charsetName) throws IOException {
216 InputStream input = null;
217
218 try {
219 input = attachment.getObject(InputStream.class);
220 return IOUtils.toString(input, charsetName);
221 } finally {
222 IOUtils.closeQuietly(input);
223 }
224 }
225
226
227
228
229
230
231
232 protected void transferAttachmentToFile(Attachment attachment, File file) throws IOException {
233 InputStream input = null;
234 OutputStream output = null;
235
236 try {
237 input = attachment.getObject(InputStream.class);
238 output = new FileOutputStream(file);
239 IOUtils.copyLarge(input, output);
240 } finally {
241 IOUtils.closeQuietly(output);
242 IOUtils.closeQuietly(input);
243 }
244 }
245
246
247
248
249
250
251 protected ResultItem recordToResultItem(ContentMigrationRecord record) {
252 ResultItem item = new ResultItem(record.getContentPath(), record.getContentType());
253 item.setSucceeded(record.isSucceeded());
254 item.setErrorMessage(record.getErrorMessage());
255 return item;
256 }
257
258
259
260
261
262
263
264
265
266
267 protected Set<String> getQueriedNodePaths(Session session, String statement, String language)
268 throws RepositoryException {
269 Set<String> nodePaths = new LinkedHashSet<>();
270 Query query = session.getWorkspace().getQueryManager().createQuery(statement, language);
271 QueryResult result = query.execute();
272
273 for (NodeIterator nodeIt = result.getNodes(); nodeIt.hasNext();) {
274 Node node = nodeIt.nextNode();
275
276 if (node != null) {
277 nodePaths.add(node.getPath());
278 }
279 }
280
281 return nodePaths;
282 }
283
284
285
286
287
288
289
290
291
292
293
294
295 protected void overrideExecutionParamsByParameters(ExecutionParams params, String batchSizeParam,
296 String throttleParam, String publishOnImportParam, String dataUrlSizeThresholdParam,
297 String docbasePropNamesParam, String documentTagsParam, String binaryTagsParam) {
298 if (StringUtils.isNotBlank(batchSizeParam)) {
299 params.setBatchSize(NumberUtils.toInt(batchSizeParam, params.getBatchSize()));
300 }
301
302 if (StringUtils.isNotBlank(throttleParam)) {
303 params.setThrottle(NumberUtils.toLong(throttleParam, params.getThrottle()));
304 }
305
306 if (StringUtils.isNotBlank(publishOnImportParam)) {
307 params.setPublishOnImport(publishOnImportParam);
308 }
309
310 if (StringUtils.isNotBlank(dataUrlSizeThresholdParam)) {
311 params.setDataUrlSizeThreshold(
312 NumberUtils.toLong(dataUrlSizeThresholdParam, params.getDataUrlSizeThreshold()));
313 }
314
315 if (StringUtils.isNotBlank(docbasePropNamesParam)) {
316 params.setDocbasePropNames(
317 new LinkedHashSet<>(Arrays.asList(StringUtils.split(docbasePropNamesParam, ","))));
318 }
319
320 if (StringUtils.isNotBlank(documentTagsParam)) {
321 params.setDocumentTags(new LinkedHashSet<>(Arrays.asList(StringUtils.split(documentTagsParam, ";"))));
322 }
323
324 if (StringUtils.isNotBlank(binaryTagsParam)) {
325 params.setBinaryTags(new LinkedHashSet<>(Arrays.asList(StringUtils.split(binaryTagsParam, ";"))));
326 }
327 }
328
329
330
331
332
333
334
335 protected Attachment getAttachmentByContentId(List<Attachment> attachments, String contentId) {
336 if (attachments == null || attachments.isEmpty()) {
337 return null;
338 }
339
340 for (Attachment attachment : attachments) {
341 if (StringUtils.equals(contentId, attachment.getContentId())) {
342 return attachment;
343 }
344 ContentDisposition contentDisposition = attachment.getContentDisposition();
345 if (contentDisposition != null && StringUtils.equals(contentId, contentDisposition.getParameter("name"))) {
346 return attachment;
347 }
348 }
349
350 return null;
351 }
352
353
354
355
356
357
358
359
360 protected boolean applyTagContentProperties(ContentNode contentNode, Set<String> tagInfos) {
361 if (CollectionUtils.isEmpty(tagInfos)) {
362 return false;
363 }
364
365 boolean updated = false;
366
367 for (String tagInfo : tagInfos) {
368 String name = StringUtils.substringBefore(tagInfo, "=");
369 String values = StringUtils.substringAfter(tagInfo, "=");
370
371 if (StringUtils.isBlank(name) || StringUtils.isBlank(values)) {
372 log.warn("Invalid content tag info: {}", tagInfo);
373 continue;
374 }
375
376 contentNode.setProperty(name, StringUtils.split(values, ","));
377 updated = true;
378 }
379
380 return updated;
381 }
382
383
384
385
386
387
388
389 protected String getUserPrincipalName(SecurityContext securityContext, HttpServletRequest request) {
390 if (securityContext != null) {
391 Principal userPrincipal = securityContext.getUserPrincipal();
392 if (userPrincipal != null) {
393 return userPrincipal.getName();
394 }
395 }
396
397 if (request != null) {
398 Principal userPrincipal = request.getUserPrincipal();
399 if (userPrincipal != null) {
400 return userPrincipal.getName();
401 }
402
403 final String authHeader = request.getHeader("Authorization");
404
405 if (StringUtils.isNotBlank(authHeader)) {
406 if (StringUtils.startsWithIgnoreCase(authHeader, "Basic ")) {
407 final String encoded = authHeader.substring(6).trim();
408 final String decoded = new String(Base64.getDecoder().decode(encoded));
409 return StringUtils.substringBefore(decoded, ":");
410 }
411 }
412 }
413
414 return null;
415 }
416
417
418
419
420
421
422
423 protected void fillProcessStatusByRequestInfo(ProcessStatus process, SecurityContext securityContext,
424 HttpServletRequest request) {
425 process.setUsername(getUserPrincipalName(securityContext, request));
426 process.setClientInfo(ServletRequestUtils.getFarthestRemoteAddr(request));
427
428 StringBuilder sbCommand = new StringBuilder(256).append(request.getMethod()).append(' ')
429 .append(request.getRequestURI());
430 String queryString = request.getQueryString();
431 if (StringUtils.isNotBlank(queryString)) {
432 sbCommand.append('?').append(queryString);
433 }
434 process.setCommandInfo(sbCommand.toString());
435 }
436
437
438
439
440
441
442
443 protected Logger createTeeLogger(final Logger mainLogger, final PrintStream secondOutput) {
444 final Logger second = new TimestampPrintStreamLogger("exim", PrintStreamLogger.INFO_LEVEL, secondOutput);
445 return new TeeLoggerWrapper(mainLogger, second);
446 }
447
448
449
450
451
452
453
454
455 protected boolean isBinaryPathIncluded(final AntPathMatcher pathMatcher, final ExecutionParams params,
456 final String path) {
457 QueriesAndPaths queriesAndPaths = params.getBinaries();
458
459 if (queriesAndPaths == null) {
460 return true;
461 }
462
463 return isPathIncluded(pathMatcher, queriesAndPaths.getExcludes(), queriesAndPaths.getIncludes(), path);
464 }
465
466
467
468
469
470
471
472
473 protected boolean isDocumentPathIncluded(final AntPathMatcher pathMatcher, final ExecutionParams params,
474 final String path) {
475 QueriesAndPaths queriesAndPaths = params.getDocuments();
476
477 if (queriesAndPaths == null) {
478 return true;
479 }
480
481 return isPathIncluded(pathMatcher, queriesAndPaths.getExcludes(), queriesAndPaths.getIncludes(), path);
482 }
483
484 private boolean isPathIncluded(final AntPathMatcher pathMatcher, final Collection<String> excludes,
485 final Collection<String> includes, final String path) {
486 if (CollectionUtils.isNotEmpty(excludes)) {
487 for (String exclude : excludes) {
488 if (pathMatcher.match(exclude, path)) {
489 return false;
490 }
491 }
492 }
493
494 if (CollectionUtils.isNotEmpty(includes)) {
495 for (String include : includes) {
496 if (pathMatcher.match(include, path)) {
497 return true;
498 }
499 }
500 return false;
501 } else {
502 return true;
503 }
504 }
505
506 private static class TimestampPrintStreamLogger extends PrintStreamLogger {
507
508 private static final FastDateFormat dateFormat = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss,SSS");
509
510 public TimestampPrintStreamLogger(final String name, final int level, final PrintStream... out)
511 throws IllegalArgumentException {
512 super(name, level, out);
513 }
514
515 @Override
516 protected String getMessageString(final String level, final String message) {
517 final String ts = dateFormat.format(System.currentTimeMillis());
518 return level + " " + ts + " " + message;
519 }
520 }
521 }