All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ch.openchvote.framework.party.State Maven / Gradle / Ivy

Go to download

This module offers a generic framework for implementing cryptographic protocols such as CHVote.

There is a newer version: 2.1
Show newest version
/*
 * Copyright (C) 2024 Berner Fachhochschule https://e-voting.bfh.ch
 *
 *  - This program is free software: you can redistribute it and/or modify                           -
 *  - it under the terms of the GNU Affero General Public License as published by                    -
 *  - the Free Software Foundation, either version 3 of the License, or                              -
 *  - (at your option) any later version.                                                            -
 *  -                                                                                                -
 *  - This program is distributed in the hope that it will be useful,                                -
 *  - but WITHOUT ANY WARRANTY; without even the implied warranty of                                 -
 *  - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the                                   -
 *  - GNU General Public License for more details.                                                   -
 *  -                                                                                                -
 *  - You should have received a copy of the GNU Affero General Public License                       -
 *  - along with this program. If not, see .                           -
 */
package ch.openchvote.framework.party;

import ch.openchvote.framework.annotations.state.Notify;
import ch.openchvote.framework.communication.*;
import ch.openchvote.framework.exceptions.CommunicationException;
import ch.openchvote.framework.exceptions.FrameworkException;
import ch.openchvote.framework.exceptions.StateException;
import ch.openchvote.framework.interfaces.Identifiable;
import ch.openchvote.framework.interfaces.UserInterface;
import ch.openchvote.framework.protocol.Phase;
import ch.openchvote.framework.services.*;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import static ch.openchvote.framework.exceptions.FrameworkException.Type.MISSING_CONSTRUCTOR;
import static ch.openchvote.framework.exceptions.FrameworkException.Type.STATE_CLASS_NOT_FOUND;
import static ch.openchvote.framework.exceptions.StateException.Type.*;

/**
 * This is a generic base class for a party's states in a protocol. The generic type parameter {@code P} represents the
 * type of the party that deals with a particular state object. State classes inheriting from {@link State} need to be
 * defined for every protocol and every party in that protocol. State transitions are implemented by registering message
 * handlers, request handlers, response handler, input handlers, and output handlers.
 *
 * @param 

The type of the state's party * @param The type of the state's event context */ public abstract class State

> implements Identifiable { // protected fields for making them accessible in subclasses protected final P party; protected final E eventContext; // several maps for storing the registered handlers private final Map> messageHandlers; private final Map> mailHandlers; private final Map> requestHandlers; private final Map> responseHandlers; private final Map> inputHandlers; private final Map> outputHandlers; private final Map> publicationHandlers; // protected constructor for internal use in subclasses protected State(P party, E eventContext) { this.party = party; this.eventContext = eventContext; this.messageHandlers = new HashMap<>(); this.mailHandlers = new HashMap<>(); this.requestHandlers = new HashMap<>(); this.responseHandlers = new HashMap<>(); this.inputHandlers = new HashMap<>(); this.outputHandlers = new HashMap<>(); this.publicationHandlers = new HashMap<>(); } // protected method to register a message handler protected void registerMessageHandler(Class contentClass, Consumer messageHandler) { var contentId = Content.getId(contentClass); if (this.messageHandlers.containsKey(contentId)) { throw new StateException(DUPLICATE_MESSAGE_HANDLER, this); } this.messageHandlers.put(contentId, messageHandler); } protected void registerMailHandler(Class contentClass, Consumer mailHandler) { var contentId = Content.getId(contentClass); if (this.mailHandlers.containsKey(contentId)) { throw new StateException(DUPLICATE_MAIL_HANDLER, this); } this.mailHandlers.put(contentId, mailHandler); } protected void registerRequestHandler(Class contentClass, Consumer requestHandler) { var contentId = Content.getId(contentClass); if (this.requestHandlers.containsKey(contentId)) { throw new StateException(DUPLICATE_REQUEST_HANDLER, this); } this.requestHandlers.put(contentId, requestHandler); } protected void registerResponseHandler(Class contentClass, Consumer responseHandler) { var contentId = Content.getId(contentClass); if (this.responseHandlers.containsKey(contentId)) { throw new StateException(DUPLICATE_RESPONSE_HANDLER, this); } this.responseHandlers.put(contentId, responseHandler); } protected void registerInputHandler(Class contentClass, Consumer inputHandler) { var contentId = Content.getId(contentClass); if (this.inputHandlers.containsKey(contentId)) { throw new StateException(DUPLICATE_INPUT_HANDLER, this); } this.inputHandlers.put(contentId, inputHandler); } protected void registerOutputHandler(Class contentClass, Consumer outputHandler) { var contentId = Content.getId(contentClass); if (this.outputHandlers.containsKey(contentId)) { throw new StateException(DUPLICATE_OUTPUT_HANDLER, this); } this.outputHandlers.put(contentId, outputHandler); } protected void registerPublicationHandler(Class contentClass, Consumer publicationHandler) { var contentId = Content.getId(contentClass); if (this.publicationHandlers.containsKey(contentId)) { throw new StateException(DUPLICATE_PUBLICATION_HANDLER, this); } this.publicationHandlers.put(contentId, publicationHandler); } /** * This method is called by the party's method {@link Party#onMessage(Message)} to trigger the processing of a * message received from another party using the party's {@link MessagingService}. State classes inheriting from * this class provide corresponding handlers for obtaining the desired behaviour. * * @param message The message received from another party * @throws CommunicationException if the current state is unable to process the message * @throws StateException if no handler exists to process the message */ public final void handleMessage(Message message) { var contentId = message.getContentId(); var messageHandler = this.messageHandlers.get(contentId); if (messageHandler == null) { throw new StateException(UNDEFINED_MESSAGE_HANDLER, this); } messageHandler.accept(message); } /** * This method is called by the party's method {@link Party#onMail(Mail)} to trigger the processing of a mail * received from another party using the party's {@link MailingService}. State classes inheriting from this class * provide corresponding handlers for obtaining the desired behaviour. * * @param mail The mail received from another party * @throws CommunicationException if the current state is unable to process the mail * @throws StateException if no handler exists to process the mail */ public final void handleMail(Mail mail) { var contentId = mail.getContentId(); var mailHandler = this.mailHandlers.get(contentId); if (mailHandler == null) { throw new StateException(UNDEFINED_MAIL_HANDLER, this); } mailHandler.accept(mail); } /** * This method is called by the party's method {@link Party#onRequest(Request)} to trigger the processing of a * request received from another party using the party's {@link RequestResponseService}. State classes inheriting * from this class provide corresponding handlers for obtaining the desired behaviour. * * @param request The request received from another party * @throws CommunicationException if the current state is unable to process the request * @throws StateException if no handler exists to process the request */ public final void handleRequest(Request request) { var contentId = request.getContentId(); var requestHandler = this.requestHandlers.get(contentId); if (requestHandler == null) { throw new StateException(UNDEFINED_REQUEST_HANDLER, this); } requestHandler.accept(request); } /** * This method is called by the party's method {@link Party#onResponse(Response)} to trigger the processing of a * response received from another party using the party's {@link RequestResponseService}. State classes inheriting * from this class provide corresponding handlers for obtaining the desired behaviour. * * @param response The response received from another party * @throws CommunicationException if the current state is unable to process the response * @throws StateException if no handler exists to process the response */ public final void handleResponse(Response response) { var contentId = response.getContentId(); var responseHandler = this.responseHandlers.get(contentId); if (responseHandler == null) { throw new StateException(UNDEFINED_RESPONSE_HANDLER, this); } responseHandler.accept(response); } /** * This method is called by the party's method {@link Party#onInput(Input)} to trigger the processing of a user * input received from the party's {@link UserInterface}. State classes inheriting from this class provide * corresponding handlers for obtaining the desired behaviour. * * @param input The received user input * @throws CommunicationException if the current state is unable to process the user input * @throws StateException if no handler exists to process the user input */ public final void handleInput(Input input) { var contentId = input.getContentId(); var inputHandler = this.inputHandlers.get(contentId); if (inputHandler == null) { throw new StateException(UNDEFINED_INPUT_HANDLER, this); } inputHandler.accept(input); } /** * This method is called by the party's method {@link Party#onOutput(Output)} to trigger the processing of an output * displayed to the party by the party's {@link UserInterface}. State classes inheriting from this class provide * corresponding handlers for obtaining the desired behaviour. * * @param output The output displayed to the party * @throws CommunicationException if the current state is unable to process the output * @throws StateException if no handler exists to process the output */ public final void handleOutput(Output output) { var contentId = output.getContentId(); var outputHandler = this.outputHandlers.get(contentId); if (outputHandler == null) { throw new StateException(UNDEFINED_OUTPUT_HANDLER, this); } outputHandler.accept(output); } /** * This method is called by the party's method {@link Party#onPublication(Publication)} to trigger the processing of * a publication received from another party using the party's {@link PublicationService}. State classes inheriting * from this class provide corresponding handlers for obtaining the desired behaviour. * * @param publication The publication displayed to the party * @throws CommunicationException if the current state is unable to process the publication * @throws StateException if no handler exists to process the publication */ public final void handlePublication(Publication publication) { var contentId = publication.getContentId(); var publicationHandler = this.publicationHandlers.get(contentId); if (publicationHandler == null) { throw new StateException(UNDEFINED_PUBLICATION_HANDLER, this); } publicationHandler.accept(publication); } /** * Default implementation of the method called by {@link Party#onStart(String, String)} for handling the "Start" * command received from the party's {@link EventService} for starting a new phase. The default implementation is * left empty, but it can be overridden by state classes inheriting from this class. */ public void handleStart() { // do nothing unless overridden } /** * Default implementation of the method called by {@link Party#onStop(String, String)} for handling the "Stop" * command received from the party's {@link EventService} for stopping the current phase. The default implementation * is left empty, but it can be overridden by state classes inheriting from this class. */ public void handleStop() { // do nothing unless overridden } /** * Default implementation of the method called by {@link Party#onSelfActivation(String, Consumer)} for handling a * self activation. The default implementation is left empty, but it can be overridden by state classes inheriting * from this class. */ public void handleSelfActivation() { // do nothing unless overridden } @Override public String getId() { return State.getId(this.getClass()); } @Override public String toString() { return this.getId(); } /** * Returns the id of the given state class. This id corresponds to the state class's fully qualified class name (the * name of the class prefixed with the package name), which is obtained by calling {@link Class#getName()}. * * @param stateClass The given state class * @return The state class's id */ static public String getId(Class stateClass) { return stateClass.getName(); } /** * Returns the print name of the given state class, which is obtained by calling {@link Class#getSimpleName()}. * * @param stateClass The given state class * @return The print name of the state class */ static public String getPrintName(Class stateClass) { return stateClass.getSimpleName(); } /** * Returns the print name of the state class for the given state id. * * @param stateId The given state id * @return The print name of the state class */ static public String getPrintName(String stateId) { return State.getPrintName(State.getClass(stateId)); } /** * Returns the state class that corresponds to the given state id. An exception is thrown, if no such class exists. * * @param stateId The given state id * @return The state class that corresponds to the given state id */ @SuppressWarnings("unchecked") static public Class> getClass(String stateId) { try { return (Class>) Class.forName(stateId); } catch (ClassNotFoundException exception) { throw new FrameworkException(STATE_CLASS_NOT_FOUND, exception, stateId); } } /** * Creates a new instance the state class for a given the given state id, party, and event context. The construction * of the state first derives the state class from the given state id and then calls the corresponding constructor * using Java reflection. An exception is thrown if constructing the instance fails for any reason. * * @param stateId The given state id * @param party The given party * @param eventContext The given event context * @return An instance of the corresponding state class */ static public State createState(String stateId, Party party, EventContext eventContext) { var stateClass = State.getClass(stateId); try { return stateClass.getConstructor(party.getClass(), eventContext.getClass()).newInstance(party, eventContext); } catch (ReflectiveOperationException exception) { throw new FrameworkException(MISSING_CONSTRUCTOR, exception, stateId); } } /** * Derives the phase assigned to a given state class based on existing phase annotations. These annotations provide * the assigned phase as value. If no annotation exists in the given state class, the placeholder phase * {@link ch.openchvote.framework.protocol.Phase.NONE} is returned. * * @param stateId The given state id * @return The phase assigned to the state class */ static public Class getAssignedPhase(String stateId) { var stateClass = State.getClass(stateId); var annotationClass = ch.openchvote.framework.annotations.state.Phase.class; return stateClass.isAnnotationPresent(annotationClass) ? stateClass.getAnnotation(annotationClass).value() : ch.openchvote.framework.protocol.Phase.NONE.class; } /** * Derives the set of status types from a given state id based on existing {@link Notify} annotations in * corresponding state classes. These annotations provide the assigned status types as value. * * @param stateId The given state id * @return The set of assigned status types */ static public Set getAssignedStatusTypes(String stateId) { var stateClass = State.getClass(stateId); // Case 1: there are multiple @Notify annotations if (stateClass.isAnnotationPresent(Notify.Annotations.class)) { return Arrays.stream(stateClass.getAnnotation(Notify.Annotations.class).value()).map(Notify::value).collect(Collectors.toSet()); } // Case 2: there is exactly one @Notify annotation if (stateClass.isAnnotationPresent(Notify.class)) { return Set.of(stateClass.getAnnotation(Notify.class).value()); } // Case 3: there are no @Notify annotations return Set.of(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy