001// SPDX-License-Identifier: GPL-3.0-or-later
002
003package es.uvigo.esei.sing.textproc.step.xml.definition;
004
005import java.lang.reflect.Modifier;
006import java.util.Collections;
007import java.util.List;
008import java.util.ServiceLoader.Provider;
009
010import javax.xml.bind.annotation.XmlAnyElement;
011import javax.xml.bind.annotation.XmlAttribute;
012import javax.xml.bind.annotation.XmlElementWrapper;
013import javax.xml.bind.annotation.XmlRootElement;
014import javax.xml.bind.annotation.XmlSeeAlso;
015import javax.xml.bind.annotation.XmlTransient;
016import javax.xml.bind.annotation.adapters.XmlAdapter;
017import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
018
019import es.uvigo.esei.sing.textproc.step.ProcessingStepService;
020import es.uvigo.esei.sing.textproc.step.ProcessingStepServices;
021import es.uvigo.esei.sing.textproc.step.internal.ProcessingStepInterface;
022import lombok.Getter;
023import lombok.NonNull;
024
025/**
026 * Models a processing step definition, suitable for marshalling and
027 * unmarshalling using JAXB.
028 *
029 * @author Alejandro González García
030 */
031@XmlRootElement(name = "step")
032@XmlSeeAlso({
033        // Common step parameter definitions
034        PageSizeProcessingStepParameter.class,
035        BatchSizeProcessingStepParameter.class,
036        TextDocumentWithTitleTableNameProcessingStepParameter.class,
037        TextDocumentTableNameProcessingStepParameter.class,
038        PrimaryKeyColumnProcessingStepParameter.class,
039        TextColumnProcessingStepParameter.class,
040        TitleColumnProcessingStepParameter.class
041        // Other step parameter definitions TBD by
042        // step service providers, and added to JAXB
043        // context at runtime
044})
045public final class ProcessingStepDefinition {
046        private static final String INVALID_STEP_MARSHAL_EXC_MESSAGE = "The specified step is not valid or accessible. Is it in the classpath?";
047
048        @XmlAttribute @XmlJavaTypeAdapter(ProcessingStepAdapter.class) @Getter
049        private ProcessingStepInterface action; // Never null due to how the adapter works
050        @XmlElementWrapper @XmlAnyElement(lax = true)
051        private List<ProcessingStepParameter> parameters; // Can be null if there are no parameters
052        @XmlTransient
053        private boolean parametersRead = false;
054
055        /**
056         * Retrieves the list of parameters given by the user for this step.
057         *
058         * @return The described list.
059         */
060        public final List<ProcessingStepParameter> getParameters() {
061                if (!parametersRead) {
062                        if (parameters == null) {
063                                parameters = Collections.emptyList();
064                        } else {
065                                // Ignore parameters that couldn't be resolved to an object
066                                parameters.removeIf(
067                                        (final Object element) -> !(element instanceof ProcessingStepParameter)
068                                );
069
070                                parameters = Collections.unmodifiableList(parameters);
071                        }
072                }
073
074                parametersRead = true;
075                return parameters;
076        }
077
078        /**
079         * Converts from a XML string to a processing step class for the purposes of
080         * JAXB, and vice versa.
081         *
082         * @author Alejandro González García
083         */
084        private static final class ProcessingStepAdapter extends XmlAdapter<String, ProcessingStepInterface> {
085                @Override
086                public ProcessingStepInterface unmarshal(@NonNull final String v) throws Exception {
087                        final ProcessingStepService stepService = ProcessingStepServices.getServiceLoader().stream()
088                                .filter((final Provider<ProcessingStepService> stepServiceProvider) ->
089                                        stepServiceProvider.get().getName().equals(v)
090                                ).findFirst().orElseThrow(
091                                        () -> new IllegalArgumentException(INVALID_STEP_MARSHAL_EXC_MESSAGE)
092                                ).get();
093
094                        return stepService.create();
095                }
096
097                @Override
098                public String marshal(@NonNull final ProcessingStepInterface v) throws Exception {
099                        final Class<? extends ProcessingStepInterface> clazz = v.getClass();
100                        final int clazzModifiers = clazz.getModifiers();
101
102                        if (Modifier.isAbstract(clazzModifiers) || Modifier.isInterface(clazzModifiers)) {
103                                throw new IllegalArgumentException(INVALID_STEP_MARSHAL_EXC_MESSAGE);
104                        }
105
106                        return clazz.getSimpleName();
107                }
108        }
109}