001/*
002 * Copyright 2008-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.springframework.batch.core.repository.dao;
017
018import java.io.IOException;
019import java.io.InputStream;
020import java.io.OutputStream;
021import java.util.Date;
022import java.util.HashMap;
023import java.util.Map;
024
025import com.fasterxml.jackson.annotation.JsonIgnore;
026import com.fasterxml.jackson.core.JsonParser;
027import com.fasterxml.jackson.core.type.TypeReference;
028import com.fasterxml.jackson.databind.DeserializationContext;
029import com.fasterxml.jackson.databind.DeserializationFeature;
030import com.fasterxml.jackson.databind.JsonNode;
031import com.fasterxml.jackson.databind.MapperFeature;
032import com.fasterxml.jackson.databind.ObjectMapper;
033
034import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
035import com.fasterxml.jackson.databind.module.SimpleModule;
036import org.springframework.batch.core.JobParameter;
037import org.springframework.batch.core.JobParameters;
038import org.springframework.batch.core.repository.ExecutionContextSerializer;
039import org.springframework.util.Assert;
040
041/**
042 * Implementation that uses Jackson2 to provide (de)serialization.
043 *
044 * @author Marten Deinum
045 * @author Mahmoud Ben Hassine
046 * @since 3.0.7
047 *
048 * @see ExecutionContextSerializer
049 */
050public class Jackson2ExecutionContextStringSerializer implements ExecutionContextSerializer {
051
052    private ObjectMapper objectMapper;
053
054    public Jackson2ExecutionContextStringSerializer() {
055        this.objectMapper = new ObjectMapper();
056        this.objectMapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false);
057        this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
058        this.objectMapper.enableDefaultTyping();
059        this.objectMapper.registerModule(new JobParametersModule());
060    }
061
062    public void setObjectMapper(ObjectMapper objectMapper) {
063        Assert.notNull(objectMapper, "ObjectMapper must not be null");
064        this.objectMapper = objectMapper;
065    }
066
067    public Map<String, Object> deserialize(InputStream in) throws IOException {
068
069        TypeReference<HashMap<String,Object>> typeRef = new TypeReference<HashMap<String,Object>>() {};
070        return objectMapper.readValue(in, typeRef);
071    }
072
073    public void serialize(Map<String, Object> context, OutputStream out) throws IOException {
074
075        Assert.notNull(context, "A context is required");
076        Assert.notNull(out, "An OutputStream is required");
077
078        objectMapper.writeValue(out, context);
079    }
080
081    // BATCH-2680
082    /**
083     * Custom Jackson module to support {@link JobParameter} and {@link JobParameters}
084     * deserialization.
085     */
086    private class JobParametersModule extends SimpleModule {
087
088        private JobParametersModule() {
089            super("Job parameters module");
090            setMixInAnnotation(JobParameters.class, JobParametersMixIn.class);
091            addDeserializer(JobParameter.class, new JobParameterDeserializer());
092        }
093
094        private abstract class JobParametersMixIn {
095            @JsonIgnore
096            abstract boolean isEmpty();
097        }
098
099        private class JobParameterDeserializer extends StdDeserializer<JobParameter> {
100
101            private static final String IDENTIFYING_KEY_NAME = "identifying";
102            private static final String TYPE_KEY_NAME = "type";
103            private static final String VALUE_KEY_NAME = "value";
104
105            JobParameterDeserializer() {
106                super(JobParameter.class);
107            }
108
109            @Override
110            public JobParameter deserialize(JsonParser parser, DeserializationContext context) throws IOException {
111                JsonNode node = parser.readValueAsTree();
112                boolean identifying = node.get(IDENTIFYING_KEY_NAME).asBoolean();
113                String type = node.get(TYPE_KEY_NAME).asText();
114                JsonNode value = node.get(VALUE_KEY_NAME);
115                Object parameterValue;
116                switch (JobParameter.ParameterType.valueOf(type)) {
117                    case STRING: {
118                        parameterValue = value.asText();
119                        return new JobParameter((String) parameterValue, identifying);
120                    }
121                    case DATE: {
122                        parameterValue = new Date(value.get(1).asLong());
123                        return new JobParameter((Date) parameterValue, identifying);
124                    }
125                    case LONG: {
126                        parameterValue = value.get(1).asLong();
127                        return new JobParameter((Long) parameterValue, identifying);
128                    }
129                    case DOUBLE: {
130                        parameterValue = value.asDouble();
131                        return new JobParameter((Double) parameterValue, identifying);
132                    }
133                }
134                return null;
135            }
136        }
137
138    }
139
140}