001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.camel.reifier.dataformat;
018
019import java.util.HashMap;
020import java.util.LinkedHashMap;
021import java.util.Map;
022import java.util.function.BiFunction;
023
024import org.apache.camel.CamelContext;
025import org.apache.camel.ExtendedCamelContext;
026import org.apache.camel.NoFactoryAvailableException;
027import org.apache.camel.RuntimeCamelException;
028import org.apache.camel.model.DataFormatDefinition;
029import org.apache.camel.model.Model;
030import org.apache.camel.model.dataformat.ASN1DataFormat;
031import org.apache.camel.model.dataformat.Any23DataFormat;
032import org.apache.camel.model.dataformat.AvroDataFormat;
033import org.apache.camel.model.dataformat.BarcodeDataFormat;
034import org.apache.camel.model.dataformat.Base64DataFormat;
035import org.apache.camel.model.dataformat.BeanioDataFormat;
036import org.apache.camel.model.dataformat.BindyDataFormat;
037import org.apache.camel.model.dataformat.CBORDataFormat;
038import org.apache.camel.model.dataformat.CryptoDataFormat;
039import org.apache.camel.model.dataformat.CsvDataFormat;
040import org.apache.camel.model.dataformat.CustomDataFormat;
041import org.apache.camel.model.dataformat.FhirDataformat;
042import org.apache.camel.model.dataformat.FhirJsonDataFormat;
043import org.apache.camel.model.dataformat.FhirXmlDataFormat;
044import org.apache.camel.model.dataformat.FlatpackDataFormat;
045import org.apache.camel.model.dataformat.GrokDataFormat;
046import org.apache.camel.model.dataformat.GzipDataFormat;
047import org.apache.camel.model.dataformat.HL7DataFormat;
048import org.apache.camel.model.dataformat.IcalDataFormat;
049import org.apache.camel.model.dataformat.JacksonXMLDataFormat;
050import org.apache.camel.model.dataformat.JaxbDataFormat;
051import org.apache.camel.model.dataformat.JsonApiDataFormat;
052import org.apache.camel.model.dataformat.JsonDataFormat;
053import org.apache.camel.model.dataformat.LZFDataFormat;
054import org.apache.camel.model.dataformat.MimeMultipartDataFormat;
055import org.apache.camel.model.dataformat.PGPDataFormat;
056import org.apache.camel.model.dataformat.ProtobufDataFormat;
057import org.apache.camel.model.dataformat.RssDataFormat;
058import org.apache.camel.model.dataformat.SoapJaxbDataFormat;
059import org.apache.camel.model.dataformat.SyslogDataFormat;
060import org.apache.camel.model.dataformat.TarFileDataFormat;
061import org.apache.camel.model.dataformat.ThriftDataFormat;
062import org.apache.camel.model.dataformat.TidyMarkupDataFormat;
063import org.apache.camel.model.dataformat.UniVocityCsvDataFormat;
064import org.apache.camel.model.dataformat.UniVocityFixedWidthDataFormat;
065import org.apache.camel.model.dataformat.UniVocityTsvDataFormat;
066import org.apache.camel.model.dataformat.XMLSecurityDataFormat;
067import org.apache.camel.model.dataformat.XStreamDataFormat;
068import org.apache.camel.model.dataformat.XmlRpcDataFormat;
069import org.apache.camel.model.dataformat.YAMLDataFormat;
070import org.apache.camel.model.dataformat.ZipDeflaterDataFormat;
071import org.apache.camel.model.dataformat.ZipFileDataFormat;
072import org.apache.camel.reifier.AbstractReifier;
073import org.apache.camel.spi.DataFormat;
074import org.apache.camel.spi.DataFormatContentTypeHeader;
075import org.apache.camel.spi.PropertyConfigurer;
076import org.apache.camel.spi.PropertyConfigurerAware;
077import org.apache.camel.spi.ReifierStrategy;
078import org.apache.camel.support.PropertyBindingSupport;
079import org.apache.camel.util.ObjectHelper;
080import org.slf4j.Logger;
081import org.slf4j.LoggerFactory;
082
083public abstract class DataFormatReifier<T extends DataFormatDefinition> extends AbstractReifier {
084
085    private static final String RESOURCE_PATH = "META-INF/services/org/apache/camel/configurer/";
086
087    private static final Logger LOG = LoggerFactory.getLogger(DataFormatReifier.class);
088
089    private static final Map<Class<? extends DataFormatDefinition>, BiFunction<CamelContext, DataFormatDefinition, DataFormatReifier<? extends DataFormatDefinition>>> DATAFORMATS;
090    static {
091        Map<Class<? extends DataFormatDefinition>, BiFunction<CamelContext, DataFormatDefinition, DataFormatReifier<? extends DataFormatDefinition>>> map = new HashMap<>();
092        map.put(Any23DataFormat.class, Any23DataFormatReifier::new);
093        map.put(ASN1DataFormat.class, ASN1DataFormatReifier::new);
094        map.put(AvroDataFormat.class, AvroDataFormatReifier::new);
095        map.put(BarcodeDataFormat.class, BarcodeDataFormatReifier::new);
096        map.put(Base64DataFormat.class, Base64DataFormatReifier::new);
097        map.put(BeanioDataFormat.class, BeanioDataFormatReifier::new);
098        map.put(BindyDataFormat.class, BindyDataFormatReifier::new);
099        map.put(CBORDataFormat.class, CBORDataFormatReifier::new);
100        map.put(CryptoDataFormat.class, CryptoDataFormatReifier::new);
101        map.put(CsvDataFormat.class, CsvDataFormatReifier::new);
102        map.put(CustomDataFormat.class, CustomDataFormatReifier::new);
103        map.put(FhirDataformat.class, FhirDataFormatReifier::new);
104        map.put(FhirJsonDataFormat.class, FhirJsonDataFormatReifier::new);
105        map.put(FhirXmlDataFormat.class, FhirXmlDataFormatReifier::new);
106        map.put(FlatpackDataFormat.class, FlatpackDataFormatReifier::new);
107        map.put(GrokDataFormat.class, GrokDataFormatReifier::new);
108        map.put(GzipDataFormat.class, GzipDataFormatReifier::new);
109        map.put(HL7DataFormat.class, HL7DataFormatReifier::new);
110        map.put(IcalDataFormat.class, IcalDataFormatReifier::new);
111        map.put(JacksonXMLDataFormat.class, JacksonXMLDataFormatReifier::new);
112        map.put(JaxbDataFormat.class, JaxbDataFormatReifier::new);
113        map.put(JsonApiDataFormat.class, JsonApiDataFormatReifier::new);
114        map.put(JsonDataFormat.class, JsonDataFormatReifier::new);
115        map.put(LZFDataFormat.class, LZFDataFormatReifier::new);
116        map.put(MimeMultipartDataFormat.class, MimeMultipartDataFormatReifier::new);
117        map.put(PGPDataFormat.class, PGPDataFormatReifier::new);
118        map.put(ProtobufDataFormat.class, ProtobufDataFormatReifier::new);
119        map.put(RssDataFormat.class, RssDataFormatReifier::new);
120        map.put(SoapJaxbDataFormat.class, SoapJaxbDataFormatReifier::new);
121        map.put(SyslogDataFormat.class, SyslogDataFormatReifier::new);
122        map.put(TarFileDataFormat.class, TarFileDataFormatReifier::new);
123        map.put(ThriftDataFormat.class, ThriftDataFormatReifier::new);
124        map.put(TidyMarkupDataFormat.class, TidyMarkupDataFormatReifier::new);
125        map.put(UniVocityCsvDataFormat.class, UniVocityCsvDataFormatReifier::new);
126        map.put(UniVocityFixedWidthDataFormat.class, UniVocityFixedWidthDataFormatReifier::new);
127        map.put(UniVocityTsvDataFormat.class, UniVocityTsvDataFormatReifier::new);
128        map.put(XmlRpcDataFormat.class, XmlRpcDataFormatReifier::new);
129        map.put(XMLSecurityDataFormat.class, XMLSecurityDataFormatReifier::new);
130        map.put(XStreamDataFormat.class, XStreamDataFormatReifier::new);
131        map.put(YAMLDataFormat.class, YAMLDataFormatReifier::new);
132        map.put(ZipDeflaterDataFormat.class, ZipDataFormatReifier::new);
133        map.put(ZipFileDataFormat.class, ZipFileDataFormatReifier::new);
134        DATAFORMATS = map;
135        ReifierStrategy.addReifierClearer(DataFormatReifier::clearReifiers);
136    }
137
138    protected final T definition;
139
140    public DataFormatReifier(CamelContext camelContext, T definition) {
141        super(camelContext);
142        this.definition = definition;
143    }
144
145    public static void registerReifier(Class<? extends DataFormatDefinition> dataFormatClass,
146                                       BiFunction<CamelContext, DataFormatDefinition, DataFormatReifier<? extends DataFormatDefinition>> creator) {
147        DATAFORMATS.put(dataFormatClass, creator);
148    }
149
150    public static void clearReifiers() {
151        DATAFORMATS.clear();
152    }
153
154    /**
155     * Factory method to create the data format
156     *
157     * @param camelContext the camel context
158     * @param type the data format type
159     * @param ref reference to lookup for a data format
160     * @return the data format or null if not possible to create
161     */
162    public static DataFormat getDataFormat(CamelContext camelContext, DataFormatDefinition type, String ref) {
163        if (type == null) {
164            ObjectHelper.notNull(ref, "ref or type");
165
166            DataFormat dataFormat = camelContext.getRegistry().lookupByNameAndType(ref, DataFormat.class);
167            if (dataFormat != null) {
168                return dataFormat;
169            }
170
171            // try to let resolver see if it can resolve it, its not always
172            // possible
173            type = camelContext.getExtension(Model.class).resolveDataFormatDefinition(ref);
174
175            if (type == null) {
176                dataFormat = camelContext.resolveDataFormat(ref);
177                if (dataFormat == null) {
178                    throw new IllegalArgumentException("Cannot find data format in registry with ref: " + ref);
179                }
180
181                return dataFormat;
182            }
183        }
184        if (type.getDataFormat() != null) {
185            return type.getDataFormat();
186        }
187        return reifier(camelContext, type).createDataFormat();
188    }
189
190    public static DataFormatReifier<? extends DataFormatDefinition> reifier(CamelContext camelContext, DataFormatDefinition definition) {
191        BiFunction<CamelContext, DataFormatDefinition, DataFormatReifier<? extends DataFormatDefinition>> reifier = DATAFORMATS.get(definition.getClass());
192        if (reifier != null) {
193            return reifier.apply(camelContext, definition);
194        }
195        throw new IllegalStateException("Unsupported definition: " + definition);
196    }
197
198    public DataFormat createDataFormat() {
199        DataFormat dataFormat = definition.getDataFormat();
200        if (dataFormat == null) {
201            dataFormat = doCreateDataFormat();
202            if (dataFormat != null) {
203                if (dataFormat instanceof DataFormatContentTypeHeader) {
204                    // is enabled by default so assume true if null
205                    final boolean contentTypeHeader = parseBoolean(definition.getContentTypeHeader(), true);
206                    ((DataFormatContentTypeHeader) dataFormat).setContentTypeHeader(contentTypeHeader);
207                }
208                // configure the rest of the options
209                configureDataFormat(dataFormat, camelContext);
210            } else {
211                throw new IllegalArgumentException("Data format '" + (definition.getDataFormatName() != null ? definition.getDataFormatName() : "<null>")
212                                                   + "' could not be created. "
213                                                   + "Ensure that the data format is valid and the associated Camel component is present on the classpath");
214            }
215        }
216        return dataFormat;
217    }
218
219    /**
220     * Factory method to create the data format instance
221     */
222    protected DataFormat doCreateDataFormat() {
223        // must use getDataFormatName() as we need special logic in json dataformat
224        String dfn = definition.getDataFormatName();
225        if (dfn != null) {
226            return camelContext.createDataFormat(dfn);
227        }
228        return null;
229    }
230
231    private String getDataFormatName() {
232        return definition.getDataFormatName();
233    }
234
235    /**
236     * Allows derived classes to customize the data format
237     */
238    protected void configureDataFormat(DataFormat dataFormat, CamelContext camelContext) {
239        Map<String, Object> properties = new LinkedHashMap<>();
240        prepareDataFormatConfig(properties);
241        properties.entrySet().removeIf(e -> e.getValue() == null);
242
243        PropertyConfigurer configurer = findPropertyConfigurer(dataFormat, camelContext);
244
245        PropertyBindingSupport.build()
246                .withCamelContext(camelContext)
247                .withTarget(dataFormat)
248                .withReference(true)
249                .withMandatory(true)
250                .withConfigurer(configurer)
251                .withProperties(properties)
252                .bind();
253    }
254
255    private PropertyConfigurer findPropertyConfigurer(DataFormat dataFormat, CamelContext camelContext) {
256        PropertyConfigurer configurer = null;
257        String name = getDataFormatName();
258        LOG.trace("Discovering optional dataformat property configurer class for dataformat: {}", name);
259        if (dataFormat instanceof PropertyConfigurerAware) {
260            configurer = ((PropertyConfigurerAware) dataFormat).getPropertyConfigurer(dataFormat);
261            if (LOG.isDebugEnabled() && configurer != null) {
262                LOG.debug("Discovered dataformat property configurer using the PropertyConfigurerAware: {} -> {}", name, configurer);
263            }
264        }
265        if (configurer == null) {
266            final String configurerName = name + "-dataformat-configurer";
267            configurer = camelContext.getRegistry().lookupByNameAndType(configurerName, PropertyConfigurer.class);
268            if (LOG.isDebugEnabled() && configurer != null) {
269                LOG.debug("Discovered dataformat property configurer using the Camel registry: {} -> {}", configurerName, configurer);
270            }
271        }
272        if (configurer == null) {
273            try {
274                Class<?> clazz = camelContext.adapt(ExtendedCamelContext.class).getFactoryFinder(RESOURCE_PATH)
275                        .findOptionalClass(name + "-dataformat-configurer", null)
276                        .orElse(null);
277                if (clazz != null) {
278                    configurer = org.apache.camel.support.ObjectHelper.newInstance(clazz, PropertyConfigurer.class);
279                    if (LOG.isDebugEnabled() && configurer != null) {
280                        LOG.debug("Discovered dataformat property configurer using the FactoryFinder: {} -> {}", name, configurer);
281                    }
282                }
283            } catch (NoFactoryAvailableException e) {
284                throw new RuntimeCamelException("Unable to retrieve dataformat property configurer factory finder", e);
285            }
286        }
287        return configurer;
288    }
289
290    protected abstract void prepareDataFormatConfig(Map<String, Object> properties);
291
292}