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}