Skip to content

Commit

Permalink
Merge pull request #278 from kagkarlsson/serializer_improvements
Browse files Browse the repository at this point in the history
Document serialization, new serializers
  • Loading branch information
kagkarlsson committed May 4, 2022
2 parents ff34915 + 58ed829 commit f79ee50
Show file tree
Hide file tree
Showing 29 changed files with 865 additions and 66 deletions.
25 changes: 24 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ scheduler.schedule(myAdhocTask.instance("1045", new MyTaskData(1001L)), Instant.
* [SchedulerClientMain.java](./examples/features/src/main/java/com/github/kagkarlsson/examples/SchedulerClientMain.java)
* [RecurringTaskWithPersistentScheduleMain.java](./examples/features/src/main/java/com/github/kagkarlsson/examples/RecurringTaskWithPersistentScheduleMain.java)
* [StatefulRecurringTaskWithPersistentScheduleMain.java](./examples/features/src/main/java/com/github/kagkarlsson/examples/StatefulRecurringTaskWithPersistentScheduleMain.java)
* [JsonSerializerMain.java](./examples/features/src/main/java/com/github/kagkarlsson/examples/JsonSerializerMain.java)


## Configuration
Expand Down Expand Up @@ -227,7 +228,9 @@ Name of the table used to track task-executions. Change name in the table defini
the table. Default `scheduled_tasks`.

:gear: `.serializer(Serializer)`<br/>
Serializer implementation to use when serializing task data. Default standard Java serialization.
Serializer implementation to use when serializing task data. Default to using standard Java serialization,
but db-scheduler also bundles a number of other Serializers (`GsonSerializer`, `JacksonSerializer`, `KotlinSerializer`).
See also additional documentation under [Serializers](#Serializers).

:gear: `.executorService(ExecutorService)`<br/>
If specified, use this externally managed executor service to run executions. Ideally the number of threads it
Expand Down Expand Up @@ -283,6 +286,26 @@ The currently available patterns are:

More details on the time zone formats can be found [here](https://docs.oracle.com/javase/8/docs/api/java/time/ZoneId.html#of-java.lang.String-).

### Serializers

A task-instance may have some associated data in the field `task_data`. The scheduler uses a `Serializer` to read and write this
data to the database. By default, standard Java serialization is used, but a number of options is provided:

* `GsonSerializer`
* `JacksonSerializer`
* `KotlinSerializer`

For Java serialization it is recommended to specify a `serialVersionUID` to be able to evolve the class representing the data. If not specified,
and the class changes, deserialization will likely fail with a `InvalidClassException`. Should this happen, find and set the current auto-generated
`serialVersionUID` explicitly. It will then be possible to do non-breaking changes to the class.

If you need to migrate from Java serialization to a `GsonSerializer`, configure the scheduler to use a `SerializerWithFallbackDeserializers`:

```java
.serializer(new SerializerWithFallbackDeserializers(new GsonSerializer(), new JavaSerializer()))
```


## Third-party task repositories

Out of the box db-scheduler supports jdbc-compliant databases. There have however been efforts to implement support for more databases via custom task repositories. It is currently a bit cumbersome plugging in a custom repository, but there are plans for making it easier.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import com.github.kagkarlsson.scheduler.Scheduler;
import com.github.kagkarlsson.scheduler.SchedulerBuilder;
import com.github.kagkarlsson.scheduler.SchedulerName;
import com.github.kagkarlsson.scheduler.Serializer;
import com.github.kagkarlsson.scheduler.serializer.Serializer;
import com.github.kagkarlsson.scheduler.boot.config.DbSchedulerCustomizer;
import com.github.kagkarlsson.scheduler.boot.config.DbSchedulerProperties;
import com.github.kagkarlsson.scheduler.boot.config.DbSchedulerStarter;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
package com.github.kagkarlsson.scheduler.boot.config;

import com.github.kagkarlsson.scheduler.SchedulerName;
import com.github.kagkarlsson.scheduler.Serializer;
import com.github.kagkarlsson.scheduler.serializer.Serializer;
import java.util.Optional;
import java.util.concurrent.ExecutorService;

Expand Down
22 changes: 22 additions & 0 deletions db-scheduler/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!-- Shaded -->
<dependency>
<groupId>com.cronutils</groupId>
<artifactId>cron-utils</artifactId>
Expand All @@ -48,11 +49,32 @@
</exclusion>
</exclusions>
</dependency>
<!-- Optional -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<optional>true</optional>
</dependency>

<!-- Test -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.github.kagkarlsson.scheduler.jdbc.JdbcCustomization;
import com.github.kagkarlsson.scheduler.jdbc.JdbcTaskRepository;
import com.github.kagkarlsson.scheduler.logging.LogLevel;
import com.github.kagkarlsson.scheduler.serializer.Serializer;
import com.github.kagkarlsson.scheduler.stats.StatsRegistry;
import com.github.kagkarlsson.scheduler.task.OnStartup;
import com.github.kagkarlsson.scheduler.task.Task;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.github.kagkarlsson.scheduler.jdbc.DefaultJdbcCustomization;
import com.github.kagkarlsson.scheduler.jdbc.JdbcCustomization;
import com.github.kagkarlsson.scheduler.jdbc.JdbcTaskRepository;
import com.github.kagkarlsson.scheduler.serializer.Serializer;
import com.github.kagkarlsson.scheduler.stats.StatsRegistry;
import com.github.kagkarlsson.scheduler.task.Execution;
import com.github.kagkarlsson.scheduler.task.SchedulableInstance;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import com.github.kagkarlsson.scheduler.Clock;
import com.github.kagkarlsson.scheduler.ScheduledExecutionsFilter;
import com.github.kagkarlsson.scheduler.SchedulerName;
import com.github.kagkarlsson.scheduler.Serializer;
import com.github.kagkarlsson.scheduler.serializer.Serializer;
import com.github.kagkarlsson.scheduler.TaskRepository;
import com.github.kagkarlsson.scheduler.TaskResolver;
import com.github.kagkarlsson.scheduler.TaskResolver.UnresolvedTask;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* Copyright (C) Gustav Karlsson
*
* Licensed 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 com.github.kagkarlsson.scheduler.serializer;

import com.github.kagkarlsson.scheduler.serializer.gson.InstantAdapter;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.function.Consumer;

public class GsonSerializer implements Serializer {
public static final Charset CHARSET = StandardCharsets.UTF_8;
private final Gson gson;

public static GsonBuilder getDefaultGson() {
return new GsonBuilder()
.serializeNulls()
.registerTypeAdapter(Instant.class, new InstantAdapter());
}

public GsonSerializer() {
this(getDefaultGson().create());
}

public GsonSerializer(Gson gson) {
this.gson = gson;
}

public GsonSerializer(Consumer<GsonBuilder> gsonCustomizer) {
GsonBuilder defaultGson = getDefaultGson();
gsonCustomizer.accept(defaultGson);
this.gson = defaultGson.create();
}

@Override
public byte[] serialize(Object object) {
return gson.toJson(object).getBytes(CHARSET);
}

@Override
public <T> T deserialize(Class<T> clazz, byte[] serializedData) {
return gson.fromJson(new String(serializedData, CHARSET), clazz);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* Copyright (C) Gustav Karlsson
*
* Licensed 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 com.github.kagkarlsson.scheduler.serializer;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.github.kagkarlsson.scheduler.exceptions.SerializationException;
import com.github.kagkarlsson.scheduler.serializer.gson.InstantAdapter;
import com.github.kagkarlsson.scheduler.serializer.jackson.InstantDeserializer;
import com.github.kagkarlsson.scheduler.serializer.jackson.InstantSerializer;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import java.io.IOException;
import java.sql.Time;
import java.time.Instant;
import java.util.function.Consumer;

public class JacksonSerializer implements Serializer {
private final ObjectMapper objectMapper;

public static ObjectMapper getDefaultObjectMapper() {
SimpleModule module = new SimpleModule();
module.addSerializer(Instant.class, new InstantSerializer());
module.addDeserializer(Instant.class, new InstantDeserializer());

return new ObjectMapper()
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
.registerModule(module);
}

public JacksonSerializer() {
this(getDefaultObjectMapper());
}

public JacksonSerializer(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}

public JacksonSerializer(Consumer<ObjectMapper> objectMapperCustomizer) {
ObjectMapper defaultObjectMapper = getDefaultObjectMapper();
objectMapperCustomizer.accept(defaultObjectMapper);
this.objectMapper = defaultObjectMapper;
}

@Override
public byte[] serialize(Object object) {
try {
return objectMapper.writeValueAsBytes(object);
} catch (JsonProcessingException e) {
throw new SerializationException("Failed to serialize object.", e);
}
}

@Override
public <T> T deserialize(Class<T> clazz, byte[] serializedData) {
try {
return objectMapper.readValue(serializedData, clazz);
} catch (IOException e) {
throw new SerializationException("Failed to deserialize object.", e);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* Copyright (C) Gustav Karlsson
*
* Licensed 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 com.github.kagkarlsson.scheduler.serializer;

import com.github.kagkarlsson.scheduler.exceptions.SerializationException;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;

public class JavaSerializer implements Serializer {

public byte[] serialize(Object data) {
if (data == null)
return null;
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutput out = new ObjectOutputStream(bos)) {
out.writeObject(data);
return bos.toByteArray();
} catch (Exception e) {
throw new SerializationException("Failed to serialize object", e);
}
}

public <T> T deserialize(Class<T> clazz, byte[] serializedData) {
if (serializedData == null)
return null;
try (ByteArrayInputStream bis = new ByteArrayInputStream(serializedData);
ObjectInput in = new ObjectInputStream(bis)) {
return clazz.cast(in.readObject());
} catch (Exception e) {
throw new SerializationException("Failed to deserialize object", e);
}
}
}
Loading

0 comments on commit f79ee50

Please sign in to comment.