-
Notifications
You must be signed in to change notification settings - Fork 467
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
) ### Proposed Changes * **Added a new REST resource (`ContentImportResource`)** to handle content import job operations, including creating and enqueuing content import jobs. * **Added a helper class (`ContentImportHelper`)** to manage content import operations, including methods for creating and managing jobs. * **Created a bean class (`ContentImportParams`)** to encapsulate multipart form parameters for content import operations. * **Introduced a form object (`ContentImportForm`)** to represent JSON parameters for content import operations. ### Checklist - [x] Tests - [x] Translations - [x] Security Implications Contemplated (add notes if applicable) ### Additional Info These changes aim to provide a REST endpoint for managing content import jobs, enabling operations like job creation and queueing.
- Loading branch information
1 parent
4f46e9e
commit f4195a7
Showing
12 changed files
with
1,796 additions
and
66 deletions.
There are no files selected for viewing
121 changes: 121 additions & 0 deletions
121
dotCMS/src/main/java/com/dotcms/rest/api/v1/JobQueueManagerHelper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
package com.dotcms.rest.api.v1; | ||
|
||
import com.dotcms.jobs.business.api.JobProcessorScanner; | ||
import com.dotcms.jobs.business.api.JobQueueManagerAPI; | ||
import com.dotcms.jobs.business.processor.JobProcessor; | ||
import com.dotcms.jobs.business.processor.Queue; | ||
import com.dotcms.util.AnnotationUtils; | ||
import com.dotmarketing.util.Logger; | ||
|
||
import javax.enterprise.context.ApplicationScoped; | ||
import javax.inject.Inject; | ||
import java.lang.reflect.Constructor; | ||
import java.util.List; | ||
import java.util.Objects; | ||
|
||
/** | ||
* Helper class for managing job queue processors in the JobQueueManagerAPI. | ||
* <p> | ||
* This class is responsible for discovering job processors, registering them with | ||
* the JobQueueManagerAPI, and shutting down the JobQueueManagerAPI when needed. | ||
*/ | ||
@ApplicationScoped | ||
public class JobQueueManagerHelper { | ||
|
||
private final JobQueueManagerAPI jobQueueManagerAPI; | ||
private final JobProcessorScanner scanner; | ||
|
||
/** | ||
* Constructor that injects the {@link JobProcessorScanner} and {@link JobQueueManagerAPI}. | ||
* | ||
* @param scanner The JobProcessorScanner to discover job processors | ||
* @param jobQueueManagerAPI The JobQueueManagerAPI instance to register processors with | ||
*/ | ||
@Inject | ||
public JobQueueManagerHelper(final JobProcessorScanner scanner, final JobQueueManagerAPI jobQueueManagerAPI) { | ||
this.scanner = scanner; | ||
this.jobQueueManagerAPI = jobQueueManagerAPI; | ||
} | ||
|
||
/** | ||
* Default constructor required by CDI. | ||
*/ | ||
public JobQueueManagerHelper() { | ||
this.scanner = null; | ||
this.jobQueueManagerAPI = null; | ||
} | ||
|
||
/** | ||
* Registers all discovered job processors with the JobQueueManagerAPI. | ||
* If the JobQueueManagerAPI is not started, it starts the API before registering the processors. | ||
*/ | ||
public void registerProcessors() { | ||
if (!jobQueueManagerAPI.isStarted()) { | ||
jobQueueManagerAPI.start(); | ||
Logger.info(this.getClass(), "JobQueueManagerAPI started"); | ||
} | ||
|
||
List<Class<? extends JobProcessor>> processors = scanner.discoverJobProcessors(); | ||
processors.forEach(processor -> { | ||
try { | ||
if (!testInstantiation(processor)) { | ||
return; | ||
} | ||
Logger.info(this.getClass(), "Registering JobProcessor: " + processor.getName()); | ||
registerProcessor(processor); | ||
} catch (Exception e) { | ||
Logger.error(this.getClass(), "Unable to register JobProcessor ", e); | ||
} | ||
}); | ||
} | ||
|
||
/** | ||
* Tests whether a given job processor can be instantiated by attempting to | ||
* create an instance of the processor using its default constructor. | ||
* | ||
* @param processor The processor class to test for instantiation | ||
* @return true if the processor can be instantiated, false otherwise | ||
*/ | ||
private boolean testInstantiation(final Class<? extends JobProcessor> processor) { | ||
try { | ||
Constructor<? extends JobProcessor> declaredConstructor = processor.getDeclaredConstructor(); | ||
declaredConstructor.newInstance(); | ||
return true; | ||
} catch (Exception e) { | ||
Logger.error(this.getClass(), String.format(" JobProcessor [%s] cannot be instantiated and will be ignored.", processor.getName()), e); | ||
} | ||
return false; | ||
} | ||
|
||
/** | ||
* Registers a job processor with the JobQueueManagerAPI using the queue name specified | ||
* in the {@link Queue} annotation, if present. If no annotation is found, the processor's | ||
* class name is used as the queue name. | ||
* | ||
* @param processor the processor class to register | ||
*/ | ||
private void registerProcessor(final Class<? extends JobProcessor> processor) { | ||
Queue queue = AnnotationUtils.getBeanAnnotation(processor, Queue.class); | ||
if (Objects.nonNull(queue)) { | ||
jobQueueManagerAPI.registerProcessor(queue.value(), processor); | ||
} else { | ||
jobQueueManagerAPI.registerProcessor(processor.getName(), processor); | ||
} | ||
} | ||
|
||
/** | ||
* Shuts down the JobQueueManagerAPI if it is currently started. | ||
* If the JobQueueManagerAPI is started, it attempts to close it gracefully. | ||
* In case of an error during the shutdown process, the error is logged. | ||
*/ | ||
public void shutdown() { | ||
if (jobQueueManagerAPI.isStarted()) { | ||
try { | ||
jobQueueManagerAPI.close(); | ||
Logger.info(this.getClass(), "JobQueueManagerAPI successfully closed"); | ||
} catch (Exception e) { | ||
Logger.error(this.getClass(), e.getMessage(), e); | ||
} | ||
} | ||
} | ||
} |
64 changes: 64 additions & 0 deletions
64
dotCMS/src/main/java/com/dotcms/rest/api/v1/content/_import/ContentImportForm.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package com.dotcms.rest.api.v1.content._import; | ||
|
||
import com.dotcms.repackage.javax.validation.constraints.NotNull; | ||
import com.dotcms.rest.api.Validated; | ||
import com.fasterxml.jackson.annotation.JsonCreator; | ||
import com.fasterxml.jackson.annotation.JsonProperty; | ||
|
||
import java.util.List; | ||
|
||
/** | ||
* Form object that represents the JSON parameters for content import operations. | ||
*/ | ||
public class ContentImportForm extends Validated { | ||
|
||
@NotNull(message = "A Content Type id or variable is required") | ||
private final String contentType; | ||
|
||
private final String language; | ||
|
||
@NotNull(message = "A Workflow Action id is required") | ||
private final String workflowActionId; | ||
|
||
private final List<String> fields; | ||
|
||
@JsonCreator | ||
public ContentImportForm( | ||
@JsonProperty("contentType") final String contentType, | ||
@JsonProperty("language") final String language, | ||
@JsonProperty("workflowActionId") final String workflowActionId, | ||
@JsonProperty("fields") final List<String> fields) { | ||
super(); | ||
this.contentType = contentType; | ||
this.language = language; | ||
this.workflowActionId = workflowActionId; | ||
this.fields = fields; | ||
this.checkValid(); | ||
} | ||
|
||
public String getContentType() { | ||
return contentType; | ||
} | ||
|
||
public String getLanguage() { | ||
return language; | ||
} | ||
|
||
public String getWorkflowActionId() { | ||
return workflowActionId; | ||
} | ||
|
||
public List<String> getFields() { | ||
return fields; | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return "ContentImportForm{" + | ||
"contentType='" + contentType + '\'' + | ||
", language='" + language + '\'' + | ||
", workflowActionId='" + workflowActionId + '\'' + | ||
", fields=" + fields + | ||
'}'; | ||
} | ||
} |
193 changes: 193 additions & 0 deletions
193
dotCMS/src/main/java/com/dotcms/rest/api/v1/content/_import/ContentImportHelper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
package com.dotcms.rest.api.v1.content._import; | ||
|
||
import com.dotcms.jobs.business.api.JobQueueManagerAPI; | ||
import com.dotcms.rest.api.v1.JobQueueManagerHelper; | ||
import com.dotcms.rest.api.v1.temp.DotTempFile; | ||
import com.dotmarketing.business.APILocator; | ||
import com.dotmarketing.business.web.WebAPILocator; | ||
import com.dotmarketing.exception.DotDataException; | ||
import com.dotmarketing.exception.DotSecurityException; | ||
import com.dotmarketing.util.Logger; | ||
import com.fasterxml.jackson.core.JsonProcessingException; | ||
import com.liferay.portal.model.User; | ||
|
||
import javax.annotation.PostConstruct; | ||
import javax.annotation.PreDestroy; | ||
import javax.enterprise.context.ApplicationScoped; | ||
import javax.inject.Inject; | ||
import javax.servlet.http.HttpServletRequest; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
/** | ||
* Helper class for managing content import operations in the dotCMS application. | ||
* <p> | ||
* This class provides methods to create and manage jobs for importing content | ||
* from external sources, such as CSV files, into the system. It handles the | ||
* validation of import parameters, processes file uploads, and constructs | ||
* the necessary job parameters to enqueue content import tasks in the job queue. | ||
*/ | ||
@ApplicationScoped | ||
public class ContentImportHelper { | ||
|
||
private final JobQueueManagerAPI jobQueueManagerAPI; | ||
private final JobQueueManagerHelper jobQueueManagerHelper; | ||
|
||
/** | ||
* Constructor for dependency injection. | ||
* | ||
* @param jobQueueManagerAPI The API for managing job queues. | ||
* @param jobQueueManagerHelper Helper for job queue management. | ||
*/ | ||
@Inject | ||
public ContentImportHelper(final JobQueueManagerAPI jobQueueManagerAPI, final JobQueueManagerHelper jobQueueManagerHelper) { | ||
this.jobQueueManagerAPI = jobQueueManagerAPI; | ||
this.jobQueueManagerHelper = jobQueueManagerHelper; | ||
} | ||
|
||
/** | ||
* Default constructor required for CDI. | ||
*/ | ||
public ContentImportHelper() { | ||
this.jobQueueManagerAPI = null; | ||
this.jobQueueManagerHelper = null; | ||
} | ||
|
||
/** | ||
* Initializes the helper by registering job processors during application startup. | ||
*/ | ||
@PostConstruct | ||
public void onInit() { | ||
jobQueueManagerHelper.registerProcessors(); | ||
} | ||
|
||
/** | ||
* Cleans up resources and shuts down the helper during application shutdown. | ||
*/ | ||
@PreDestroy | ||
public void onDestroy() { | ||
jobQueueManagerHelper.shutdown(); | ||
} | ||
|
||
/** | ||
* Creates a content import job with the provided parameters and submits it to the job queue. | ||
* | ||
* @param command The command indicating the type of operation (e.g., "preview" or "import"). | ||
* @param queueName The name of the queue to which the job should be submitted. | ||
* @param params The content import parameters containing the details of the import operation. | ||
* @param user The user initiating the import. | ||
* @param request The HTTP request associated with the import operation. | ||
* @return The ID of the created job. | ||
* @throws DotDataException If there is an error creating the job. | ||
* @throws JsonProcessingException If there is an error processing JSON data. | ||
*/ | ||
public String createJob( | ||
final String command, | ||
final String queueName, | ||
final ContentImportParams params, | ||
final User user, | ||
final HttpServletRequest request) throws DotDataException, JsonProcessingException { | ||
|
||
params.checkValid(); | ||
final Map<String, Object> jobParameters = createJobParameters(command, params, user, request); | ||
processFileUpload(params, jobParameters, request); | ||
|
||
return jobQueueManagerAPI.createJob(queueName, jobParameters); | ||
} | ||
|
||
/** | ||
* Constructs a map of job parameters based on the provided inputs. | ||
* | ||
* @param command The command indicating the type of operation. | ||
* @param params The content import parameters. | ||
* @param user The user initiating the import. | ||
* @param request The HTTP request associated with the operation. | ||
* @return A map containing the job parameters. | ||
* @throws JsonProcessingException If there is an error processing JSON data. | ||
*/ | ||
private Map<String, Object> createJobParameters( | ||
final String command, | ||
final ContentImportParams params, | ||
final User user, | ||
final HttpServletRequest request) throws JsonProcessingException { | ||
|
||
final Map<String, Object> jobParameters = new HashMap<>(); | ||
|
||
// Add required parameters | ||
jobParameters.put("cmd", command); | ||
jobParameters.put("userId", user.getUserId()); | ||
jobParameters.put("contentType", params.getForm().getContentType()); | ||
jobParameters.put("workflowActionId", params.getForm().getWorkflowActionId()); | ||
|
||
// Add optional parameters | ||
addOptionalParameters(params, jobParameters); | ||
|
||
// Add site information | ||
addSiteInformation(request, jobParameters); | ||
|
||
return jobParameters; | ||
} | ||
|
||
/** | ||
* Adds optional parameters to the job parameter map if they are present in the form. | ||
* | ||
* @param params The content import parameters. | ||
* @param jobParameters The map of job parameters to which optional parameters are added. | ||
* @throws JsonProcessingException If there is an error processing JSON data. | ||
*/ | ||
private void addOptionalParameters( | ||
final ContentImportParams params, | ||
final Map<String, Object> jobParameters) throws JsonProcessingException { | ||
|
||
final ContentImportForm form = params.getForm(); | ||
|
||
if (form.getLanguage() != null && !form.getLanguage().isEmpty()) { | ||
jobParameters.put("language", form.getLanguage()); | ||
} | ||
if (form.getFields() != null && !form.getFields().isEmpty()) { | ||
jobParameters.put("fields", form.getFields()); | ||
} | ||
} | ||
|
||
/** | ||
* Adds the current site information to the job parameters. | ||
* | ||
* @param request The HTTP request associated with the operation. | ||
* @param jobParameters The map of job parameters to which site information is added. | ||
*/ | ||
private void addSiteInformation( | ||
final HttpServletRequest request, | ||
final Map<String, Object> jobParameters){ | ||
|
||
final var currentHost = WebAPILocator.getHostWebAPI().getCurrentHostNoThrow(request); | ||
jobParameters.put("siteName", currentHost.getHostname()); | ||
jobParameters.put("siteIdentifier", currentHost.getIdentifier()); | ||
} | ||
|
||
/** | ||
* Processes the file upload and adds the file-related parameters to the job. | ||
* | ||
* @param params The content import parameters. | ||
* @param jobParameters The map of job parameters. | ||
* @param request The HTTP request containing the uploaded file. | ||
* @throws DotDataException If there is an error processing the file upload. | ||
*/ | ||
private void processFileUpload( | ||
final ContentImportParams params, | ||
final Map<String, Object> jobParameters, | ||
final HttpServletRequest request) throws DotDataException { | ||
|
||
try { | ||
final DotTempFile tempFile = APILocator.getTempFileAPI().createTempFile( | ||
params.getContentDisposition().getFileName(), | ||
request, | ||
params.getFileInputStream() | ||
); | ||
jobParameters.put("tempFileId", tempFile.id); | ||
jobParameters.put("requestFingerPrint", APILocator.getTempFileAPI().getRequestFingerprint(request)); | ||
} catch (DotSecurityException e) { | ||
Logger.error(this, "Error handling file upload", e); | ||
throw new DotDataException("Error processing file upload: " + e.getMessage()); | ||
} | ||
} | ||
} |
Oops, something went wrong.