How to create a calendar-based schedule using jberet-ui?

219 views Asked by At

Im using jberet-ui (built from master branch of https://github.com/jberet/jberet-ui.git) embedded in a shaded war with jberet-rest-api, jberet-rest-common, jberet-schedule-executor and jberet-schedule-timer version 1.4.0.Final as dependencies.

When I try to create a calendar-based schedule, I get a 400 response from the api error response with a message 'Failed to schedule job execution for job: ag-insurance-import-lisa-subscriptions.' displayed in the bottom of the page, and the text

Unrecognized field "hour" (class javax.ejb.ScheduleExpression), not marked as ignorable

in the response body.

How is this javax.ejb.ScheduleExpression supposed to be deserialized? It does not seem like a simple pojo that could be simply bound to a json model, and I couldn't find any deserializer in the jberet-rest* projects. Am I supposed to provide my own json (de)serializers?

2

There are 2 answers

0
cghislai On

To work around this issue, currently, the following works.

war dependencies:

     <dependency>
        <groupId>javax</groupId>
        <artifactId>javaee-api</artifactId>
        <version>8.0.1</version>
        <scope>provided</scope>
    </dependency>

    <dependency>
        <groupId>org.jberet</groupId>
        <artifactId>jberet-rest-commons</artifactId>
        <version>1.4.0.Final</version>
    </dependency>
    <dependency>
        <groupId>org.jberet</groupId>
        <artifactId>jberet-schedule-executor</artifactId>
        <version>1.4.0.Final</version>
    </dependency>
    <dependency>
        <groupId>org.jberet</groupId>
        <artifactId>jberet-schedule-timer</artifactId>
        <version>1.4.0.Final</version>
    </dependency>
    <dependency>
        <groupId>org.jberet</groupId>
        <artifactId>jberet-rest-api</artifactId>
        <version>1.4.0.Final</version>
    </dependency>

Then create a json deserializer:

 public class ScheduleExpressionDeserializer extends JsonDeserializer<ScheduleExpression> {

    private final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ISO_OFFSET_DATE;

    @Override
    public ScheduleExpression deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
        ScheduleExpression scheduleExpression = new ScheduleExpression();

        // Payload example:
        // {"year":"1","month":"2","dayOfMonth":"3","dayOfWeek":"4",
        // "hour":"5","minute":"6","start":"2020-05-04T08:10:00.000Z",
        // "end":"2020-06-05T08:12:00.000Z","timezone":"Africa/Blantyre"}
        TreeNode treeNode = jsonParser.getCodec().readTree(jsonParser);

        Optional.ofNullable(treeNode.get("year"))
                .flatMap(this::parseIntegerNode)
                .ifPresent(scheduleExpression::year);

        Optional.ofNullable(treeNode.get("month"))
                .flatMap(this::parseIntegerNode)
                .ifPresent(scheduleExpression::month);

        Optional.ofNullable(treeNode.get("dayOfMonth"))
                .flatMap(this::parseIntegerNode)
                .ifPresent(scheduleExpression::dayOfMonth);

        Optional.ofNullable(treeNode.get("dayOfWeek"))
                .flatMap(this::parseIntegerNode)
                .ifPresent(scheduleExpression::dayOfWeek);

        Optional.ofNullable(treeNode.get("hour"))
                .flatMap(this::parseIntegerNode)
                .ifPresent(scheduleExpression::hour);

        Optional.ofNullable(treeNode.get("minute"))
                .flatMap(this::parseIntegerNode)
                .ifPresent(scheduleExpression::minute);


        Optional.ofNullable(treeNode.get("start"))
                .flatMap(this::parseDateTimeNode)
                .ifPresent(scheduleExpression::start);

        Optional.ofNullable(treeNode.get("end"))
                .flatMap(this::parseDateTimeNode)
                .ifPresent(scheduleExpression::end);

        Optional.ofNullable(treeNode.get("timezone"))
                .map(TreeNode::asToken)
                .map(JsonToken::asString)
                .ifPresent(scheduleExpression::timezone);

        return scheduleExpression;
    }

    private Optional<Integer> parseIntegerNode(TreeNode node) {
        if (node instanceof TextNode) {
            TextNode textNode = (TextNode) node;
            return Optional.of(textNode)
                    .map(TextNode::asInt);
        } else {
            return Optional.empty();
        }
    }


    private Optional<Date> parseDateTimeNode(TreeNode node) {
        if (node instanceof TextNode) {
            TextNode textNode = (TextNode) node;
            return Optional.of(textNode)
                    .map(TextNode::asText)
                    .map(s -> OffsetDateTime.parse(s, dateTimeFormatter))
                    .map(OffsetDateTime::toInstant)
                    .map(Date::from);
        } else {
            return Optional.empty();
        }
    }
}

And use it in a jax-rs-provided ObjectMapper:

@Provider
public class JacksonMapperResolver implements ContextResolver<ObjectMapper> {

    @Override
    public ObjectMapper getContext(Class<?> type) {
        ObjectMapper mapper = new ObjectMapper();

        SimpleModule simpleModule = new SimpleModule("Custom deserializers");
        simpleModule.addDeserializer(ScheduleExpression.class, new ScheduleExpressionDeserializer());
        mapper.registerModule(simpleModule);
        return mapper;
    }
}
2
cheng On

There is a sample batch app (scheduleTimer) with jberet-ui, and you may want to check it out.

I've never seen this error before. It could be related to some changes across different jackson library versions (used for json binding). You may want to try the exact version of jackson-* dependencies as used in the above jberet sample project.

The line of code in question is in JobScheduleConfig class.

Can you share error details and stacktrace from WildFly server.log?