Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
import org.apache.fineract.client.feign.FineractFeignClient;
import org.apache.fineract.client.models.ExecuteJobRequest;
import org.apache.fineract.client.models.GetJobsResponse;
import org.apache.fineract.client.models.PutJobsJobIDRequest;
import org.apache.fineract.client.models.JobUpdateRequest;
import org.springframework.stereotype.Component;

@Slf4j
Expand Down Expand Up @@ -126,7 +126,7 @@ private Long updateExternalEventJobFrequency(String cronExpression) {
.filter(r -> r.getDisplayName().equals(SEND_ASYNCHRONOUS_EVENTS_JOB_NAME)).findAny()
.orElseThrow(() -> new IllegalStateException(SEND_ASYNCHRONOUS_EVENTS_JOB_NAME + " is not found"));
Long jobId = externalEventJobResponse.getJobId();
executeVoid(() -> fineractClient.schedulerJob().updateJobDetail(jobId, new PutJobsJobIDRequest().cronExpression(cronExpression)));
executeVoid(() -> fineractClient.schedulerJob().updateJobDetail(jobId, new JobUpdateRequest().cronExpression(cronExpression)));
return jobId;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.command.core.CommandDispatcher;
import org.apache.fineract.commands.domain.CommandWrapper;
import org.apache.fineract.commands.service.CommandWrapperBuilder;
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
Expand All @@ -62,8 +63,11 @@
import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
import org.apache.fineract.infrastructure.core.service.Page;
import org.apache.fineract.infrastructure.core.service.SearchParameters;
import org.apache.fineract.infrastructure.jobs.command.JobUpdateCommand;
import org.apache.fineract.infrastructure.jobs.data.JobDetailData;
import org.apache.fineract.infrastructure.jobs.data.JobDetailHistoryData;
import org.apache.fineract.infrastructure.jobs.data.JobUpdateRequest;
import org.apache.fineract.infrastructure.jobs.data.JobUpdateResponse;
import org.apache.fineract.infrastructure.jobs.service.JobRegisterService;
import org.apache.fineract.infrastructure.jobs.service.SchedulerJobRunnerReadService;
import org.apache.fineract.infrastructure.security.exception.NoAuthorizationException;
Expand All @@ -87,6 +91,7 @@ public class SchedulerJobApiResource {
private final PlatformSecurityContext context;
private final FineractProperties fineractProperties;
private final SqlValidator sqlValidator;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this something that should reside in a service? Or is this validation like in Jakarta Validation of input parameters? SqlValidator is a very low level implementation detail that we shouldn't reveal in the REST layer... my 2 cents.

private final CommandDispatcher dispatcher;

@GET
@Operation(summary = "Retrieve Scheduler Jobs", operationId = "retrieveAllSchedulerJobs", description = "Returns the list of jobs.\n"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like authorization enforcement is missing. In the legacy code these checks are usually done by using PlatformSecurityContext; we really shouldn't, because it obscures the security configuration unnecessarily and makes it really hard if we need to investigate or audit things. So, good that it's removed here... but we have to provide the - correct - equivalent. Please see here

how to do this. And of course this needs to be done for every endpoint that is exposed by this resource.

Expand Down Expand Up @@ -173,14 +178,14 @@ public Response executeJobByShortName(
}

@PUT
@Path("{" + SchedulerJobApiConstants.JOB_ID + "}")
@Consumes({ MediaType.APPLICATION_JSON })
@Path("{jobId}")
@Consumes(MediaType.APPLICATION_JSON)
@Operation(summary = "Update a Job", description = "Updates the details of a job.")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = SchedulerJobApiResourceSwagger.PutJobsJobIDRequest.class)))
@ApiResponse(responseCode = "200", description = "OK")
public String updateJobDetail(@PathParam(SchedulerJobApiConstants.JOB_ID) @Parameter(description = "jobId") final Long jobId,
@Parameter(hidden = true) final String jsonRequestBody) {
return updateJobDetail(IdTypeResolver.resolveDefault(), Objects.toString(jobId, null), jsonRequestBody);
public JobUpdateResponse updateJobDetail(@PathParam("jobId") Long jobId, JobUpdateRequest request) {
request.setJobId(jobId);
var command = new JobUpdateCommand();
command.setPayload(request);
return dispatcher.<JobUpdateRequest, JobUpdateResponse>dispatch(command).get();
}

@PUT
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.apache.fineract.infrastructure.jobs.command;

import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.fineract.command.core.Command;
import org.apache.fineract.infrastructure.jobs.data.JobUpdateRequest;

@Data
@EqualsAndHashCode(callSuper = true)
public class JobUpdateCommand extends Command<JobUpdateRequest> {}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.apache.fineract.infrastructure.jobs.data;

import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.validation.constraints.AssertTrue;
import java.io.Serial;
import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.FieldNameConstants;
import org.quartz.CronExpression;

@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
@FieldNameConstants
public class JobUpdateRequest implements Serializable {

@Serial
private static final long serialVersionUID = 1L;

private Long jobId;

private String displayName;
private String cronExpression;
private Boolean active;

@JsonIgnore
@AssertTrue(message = "{org.apache.fineract.infrastructure.jobs.update.at-least-one-field}")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, two things:

  1. Translation key: how about this pattern:

org.apache.fineract.infrastructure.job.update.assertion.at-least-one ... and below...
org.apache.fineract.infrastructure.job.update.assertion.cron-expression

This keeps things short and we have the same number of segments in both keys ("assertions" tells us already that something is validated, so we don't have to use words like "invalid" and similar). Not earth shattering differences, but keeps everything compact and concise.

  1. Did you actually provide translations for these keys? I don't see any changes in https://github.com/apache/fineract/blob/develop/fineract-validation/src/main/resources/ValidationMessages.properties; please fix.

public boolean isAtLeastOneFieldPresent() {
return displayName != null || cronExpression != null || active != null;
}

@JsonIgnore
@AssertTrue(message = "{org.apache.fineract.infrastructure.jobs.cron-expression.invalid}")
public boolean isCronExpressionValid() {
return cronExpression == null || CronExpression.isValidExpression(cronExpression.trim());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.apache.fineract.infrastructure.jobs.data;

import java.io.Serial;
import java.io.Serializable;
import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
public class JobUpdateResponse implements Serializable {

@Serial
private static final long serialVersionUID = 1L;

private Long resourceId; // the jobId
private Map<String, Object> changes; // what actually changed — kept for backward compatibility
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.apache.fineract.infrastructure.jobs.handler;

import io.github.resilience4j.retry.annotation.Retry;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.command.core.Command;
import org.apache.fineract.command.core.CommandHandler;
import org.apache.fineract.infrastructure.jobs.data.JobUpdateRequest;
import org.apache.fineract.infrastructure.jobs.data.JobUpdateResponse;
import org.apache.fineract.infrastructure.jobs.service.JobRegisterService;
import org.apache.fineract.infrastructure.jobs.service.SchedularWritePlatformService;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Slf4j
@Component
@RequiredArgsConstructor
public class JobUpdateCommandHandler implements CommandHandler<JobUpdateRequest, JobUpdateResponse> {

private final SchedularWritePlatformService schedularWritePlatformService;
private final JobRegisterService jobRegisterService;

@Retry(name = "commandJobUpdate", fallbackMethod = "fallback")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the resilience configuration for this is missing? Please have a look at this here to see how this works:

. Please add missing entries for this command handler.

@Override
@Transactional
public JobUpdateResponse handle(Command<JobUpdateRequest> command) {
JobUpdateResponse response = schedularWritePlatformService.updateJobDetail(command.getPayload());

Map<String, Object> changes = response.getChanges();
if (changes != null && (changes.containsKey("cronExpression") || changes.containsKey("active"))) {
jobRegisterService.rescheduleJob(command.getPayload().getJobId());
}
return response;
}

@Override
public JobUpdateResponse fallback(Command<JobUpdateRequest> command, Throwable t) {
return CommandHandler.super.fallback(command, t);
}
}
Loading
Loading