Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor actions JSON serialization / deserialization + plugin mechanism #2325

Merged
merged 5 commits into from
Oct 18, 2022
Merged
Show file tree
Hide file tree
Changes from 4 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
35 changes: 32 additions & 3 deletions commons/src/main/java/com/powsybl/commons/json/JsonUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -433,21 +433,49 @@ public static void assertLessThanReferenceVersion(String contextName, String ele
}
}

/**
* Called by variants of {@link #parseObject} on each encountered field.
* Should return false if an unexpected field was encountered.
*/
@FunctionalInterface
public interface FieldHandler {

boolean onField(String name) throws IOException;
}

/**
* Parses an object from the current parser position, using the provided field handler.
* The parsing will expect the starting position to be START_OBJECT.
*/
public static void parseObject(JsonParser parser, FieldHandler fieldHandler) {
parseObject(parser, false, fieldHandler);
}

/**
* Parses an object from the current parser position, using the provided field handler.
* The parsing will accept the starting position to be either a START_OBJECT or a FIELD_NAME,
* see contract for {@link JsonDeserializer#deserialize(JsonParser, DeserializationContext)}.
*/
public static void parsePolymorphicObject(JsonParser parser, FieldHandler fieldHandler) {
parseObject(parser, true, fieldHandler);
}

/**
* Parses an object from the current parser position, using the provided field handler.
* If {@code polymorphic} is {@code true}, the parsing will accept the starting position
* to be either a START_OBJECT or a FIELD_NAME, see contract for {@link JsonDeserializer#deserialize(JsonParser, DeserializationContext)}.
*/
public static void parseObject(JsonParser parser, boolean polymorphic, FieldHandler fieldHandler) {
Objects.requireNonNull(parser);
Objects.requireNonNull(fieldHandler);
try {
JsonToken token = parser.currentToken();
if (token != JsonToken.START_OBJECT) {
if (!polymorphic && token != JsonToken.START_OBJECT) {
throw new PowsyblException("Start object token was expected instead got: " + token);
}
while ((token = parser.nextToken()) != null) {
if (token == JsonToken.START_OBJECT) {
token = parser.nextToken();
}
while (token != null) {
if (token == JsonToken.FIELD_NAME) {
String fieldName = parser.getCurrentName();
boolean found = fieldHandler.onField(fieldName);
Expand All @@ -459,6 +487,7 @@ public static void parseObject(JsonParser parser, FieldHandler fieldHandler) {
} else {
throw new PowsyblException("Unexpected token " + token);
}
token = parser.nextToken();
}
} catch (IOException e) {
throw new UncheckedIOException(e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,50 @@
*/
package com.powsybl.security.json;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.jsontype.NamedType;
import com.powsybl.commons.util.ServiceLoaderCache;
import com.powsybl.contingency.json.ContingencyJsonModule;
import com.powsybl.security.*;
import com.powsybl.security.action.Action;
import com.powsybl.security.action.ActionList;
import com.powsybl.security.action.*;
import com.powsybl.security.condition.Condition;
import com.powsybl.security.json.action.ActionDeserializer;
import com.powsybl.security.json.action.ActionListDeserializer;
import com.powsybl.security.json.action.ActionListSerializer;
import com.powsybl.security.json.action.ActionSerializer;
import com.powsybl.security.json.action.*;
import com.powsybl.security.results.*;
import com.powsybl.security.strategy.OperatorStrategy;
import com.powsybl.security.strategy.OperatorStrategyList;
import com.powsybl.security.results.*;

import java.util.Collection;
import java.util.List;
import java.util.Objects;

/**
* @author Geoffroy Jamgotchian <geoffroy.jamgotchian at rte-france.com>
*/
public class SecurityAnalysisJsonModule extends ContingencyJsonModule {

private final List<SecurityAnalysisJsonPlugin> plugins;

public SecurityAnalysisJsonModule() {
this(getServices());
}

private static List<SecurityAnalysisJsonPlugin> getServices() {
return new ServiceLoaderCache<>(SecurityAnalysisJsonPlugin.class).getServices();
}

/**
* Deserializer for actions will be chosen based on the "type" property.
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", include = JsonTypeInfo.As.EXISTING_PROPERTY, visible = true)
interface ActionMixIn {
}

public SecurityAnalysisJsonModule(Collection<SecurityAnalysisJsonPlugin> plugins) {
Objects.requireNonNull(plugins);
this.plugins = List.copyOf(plugins);
addSerializer(SecurityAnalysisResult.class, new SecurityAnalysisResultSerializer());
addSerializer(NetworkMetadata.class, new NetworkMetadataSerializer());
addSerializer(PostContingencyResult.class, new PostContingencyResultSerializer());
Expand All @@ -38,7 +63,6 @@ public SecurityAnalysisJsonModule() {
addSerializer(OperatorStrategyResult.class, new OperatorStrategyResultSerializer());
addSerializer(OperatorStrategy.class, new OperatorStrategySerializer());
addSerializer(OperatorStrategyList.class, new OperatorStrategyListSerializer());
addSerializer(Action.class, new ActionSerializer());
addSerializer(ActionList.class, new ActionListSerializer());
addSerializer(Condition.class, new ConditionSerializer());

Expand All @@ -55,9 +79,35 @@ public SecurityAnalysisJsonModule() {
addDeserializer(OperatorStrategyResult.class, new OperatorStrategyResultDeserializer());
addDeserializer(OperatorStrategy.class, new OperatorStrategyDeserializer());
addDeserializer(OperatorStrategyList.class, new OperatorStrategyListDeserializer());
addDeserializer(Action.class, new ActionDeserializer());
addDeserializer(ActionList.class, new ActionListDeserializer());
addDeserializer(Condition.class, new ConditionDeserializer());
addDeserializer(NetworkResult.class, new NetworkResultDeserializer());

configureActionsSerialization();
}

private void configureActionsSerialization() {
setMixInAnnotation(Action.class, ActionMixIn.class);
registerActionType(SwitchAction.class, SwitchAction.NAME,
new SwitchActionSerializer(), new SwitchActionDeserializer());
registerActionType(LineConnectionAction.class, LineConnectionAction.NAME,
new LineConnectionActionSerializer(), new LineConnectionActionDeserializer());
registerActionType(MultipleActionsAction.class, MultipleActionsAction.NAME,
new MultipleActionsActionSerializer(), new MultipleActionsActionDeserializer());
registerActionType(PhaseTapChangerTapPositionAction.class, PhaseTapChangerTapPositionAction.NAME,
new PhaseTapChangerTapPositionActionSerializer(), new PhaseTapChangerTapPositionActionDeserializer());
}

private <T> void registerActionType(Class<T> actionClass, String typeName, JsonSerializer<T> serializer, JsonDeserializer<T> deserializer) {
registerSubtypes(new NamedType(actionClass, typeName));
addDeserializer(actionClass, deserializer);
addSerializer(actionClass, serializer);
}

@Override
public Iterable<? extends Module> getDependencies() {
return () -> plugins.stream()
.flatMap(plugin -> plugin.getJsonModules().stream())
.iterator();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* Copyright (c) 2022, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package com.powsybl.security.json;

import com.fasterxml.jackson.databind.Module;

import java.util.List;

/**
* @author Sylvain Leclerc <sylvain.leclerc@rte-france.com>
*/
public interface SecurityAnalysisJsonPlugin {

/**
* Provide third-party jackson modules that should be registered as dependencies of the
* main security analysis module.
*
* @return A list of modules to be registered for use in security analysis related serialization.
*/
List<Module> getJsonModules();
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
package com.powsybl.security.json.action;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.powsybl.commons.json.JsonUtil;
import com.powsybl.security.action.LineConnectionAction;

import java.io.IOException;
Expand All @@ -34,26 +34,31 @@ private static class ParsingContext {
@Override
public LineConnectionAction deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
ParsingContext context = new ParsingContext();
while (jsonParser.nextToken() != JsonToken.END_OBJECT) {
switch (jsonParser.getCurrentName()) {
JsonUtil.parsePolymorphicObject(jsonParser, name -> {
switch (name) {
case "type":
if (!LineConnectionAction.NAME.equals(jsonParser.nextTextValue())) {
throw JsonMappingException.from(jsonParser, "Expected type " + LineConnectionAction.NAME);
}
return true;
case "id":
context.id = jsonParser.nextTextValue();
break;
return true;
case "lineId":
context.lineId = jsonParser.nextTextValue();
break;
return true;
case "openSide1":
jsonParser.nextToken();
context.openSide1 = jsonParser.getValueAsBoolean();
break;
return true;
case "openSide2":
jsonParser.nextToken();
context.openSide2 = jsonParser.getValueAsBoolean();
break;
return true;
default:
throw new IllegalArgumentException("");
return false;
}
}
});
if (context.openSide1 == null || context.openSide2 == null) {
throw JsonMappingException.from(jsonParser, "for line action openSide1 and openSide2 fields can't be null");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* Copyright (c) 2022, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package com.powsybl.security.json.action;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.powsybl.security.action.LineConnectionAction;

import java.io.IOException;

/**
* @author Etienne Lesot <etienne.lesot@rte-france.com>
*/
public class LineConnectionActionSerializer extends StdSerializer<LineConnectionAction> {

public LineConnectionActionSerializer() {
super(LineConnectionAction.class);
}

@Override
public void serialize(LineConnectionAction action, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeStartObject();
jsonGenerator.writeStringField("type", action.getType());
jsonGenerator.writeStringField("id", action.getId());
jsonGenerator.writeStringField("lineId", action.getLineId());
jsonGenerator.writeBooleanField("openSide1", action.isOpenSide1());
jsonGenerator.writeBooleanField("openSide2", action.isOpenSide2());
jsonGenerator.writeEndObject();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
package com.powsybl.security.json.action;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.powsybl.commons.json.JsonUtil;
import com.powsybl.security.action.Action;
import com.powsybl.security.action.MultipleActionsAction;

Expand All @@ -35,20 +36,25 @@ private static class ParsingContext {
@Override
public MultipleActionsAction deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
ParsingContext context = new ParsingContext();
while (jsonParser.nextToken() != JsonToken.END_OBJECT) {
switch (jsonParser.getCurrentName()) {
JsonUtil.parsePolymorphicObject(jsonParser, name -> {
switch (name) {
case "type":
if (!MultipleActionsAction.NAME.equals(jsonParser.nextTextValue())) {
throw JsonMappingException.from(jsonParser, "Expected type " + MultipleActionsAction.NAME);
}
return true;
case "id":
context.id = jsonParser.nextTextValue();
break;
return true;
case "actions":
jsonParser.nextToken();
context.actions = jsonParser.readValueAs(new TypeReference<ArrayList<Action>>() {
});
break;
return true;
default:
throw new IllegalArgumentException("");
return false;
}
}
});
return new MultipleActionsAction(context.id, context.actions);
}
}
Loading