JaxbContextWrapper.java

/*
 * Copyright 2020-2022  the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.bremersee.xml;

import static org.springframework.util.ObjectUtils.isEmpty;

import jakarta.xml.bind.Binder;
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.JAXBIntrospector;
import jakarta.xml.bind.Marshaller;
import jakarta.xml.bind.SchemaOutputResolver;
import jakarta.xml.bind.Unmarshaller;
import jakarta.xml.bind.ValidationEventHandler;
import jakarta.xml.bind.annotation.adapters.XmlAdapter;
import jakarta.xml.bind.attachment.AttachmentMarshaller;
import jakarta.xml.bind.attachment.AttachmentUnmarshaller;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
import javax.xml.validation.Schema;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import lombok.ToString;
import org.springframework.util.Assert;

/**
 * This {@link JAXBContext} will be returned by the {@link JaxbContextBuilder}.
 *
 * @author Christian Bremer
 */
@EqualsAndHashCode(callSuper = false)
@ToString(exclude = {"jaxbContext"})
public class JaxbContextWrapper extends JAXBContext {

  private final JAXBContext jaxbContext;

  @Getter
  private final JaxbContextDetails details;

  @Getter
  @Setter
  private boolean formattedOutput;

  @Getter
  @Setter
  private List<XmlAdapter<?, ?>> xmlAdapters;

  @Getter
  @Setter
  private AttachmentMarshaller attachmentMarshaller;

  @Getter
  @Setter
  private AttachmentUnmarshaller attachmentUnmarshaller;

  @Getter
  @Setter
  private ValidationEventHandler validationEventHandler;

  @Getter
  @Setter
  private Schema schema;

  @Getter
  @Setter
  @NonNull
  private SchemaMode schemaMode = SchemaMode.NEVER;

  /**
   * Instantiates a new jaxb context wrapper.
   *
   * @param jaxbContext the jaxb context
   */
  public JaxbContextWrapper(
      JAXBContext jaxbContext) {
    this(jaxbContext, null);
  }

  /**
   * Instantiates a new jaxb context wrapper.
   *
   * @param data the data
   * @param classLoaders the class loaders
   * @throws JAXBException the jaxb exception
   */
  public JaxbContextWrapper(Stream<JaxbContextData> data, ClassLoader... classLoaders)
      throws JAXBException {
    Assert.notNull(data, "Stream of jaxb context data must be present.");
    this.details = data.collect(JaxbContextDetails.contextDataCollector());
    Assert.isTrue(!details.isEmpty(), "There is no jaxb model.");
    this.jaxbContext = newInstance(this.details.getClasses(classLoaders));
  }

  /**
   * Instantiates a new jaxb context wrapper.
   *
   * @param jaxbContext the jaxb context
   * @param details the details
   */
  JaxbContextWrapper(
      JAXBContext jaxbContext,
      JaxbContextDetails details) {
    Assert.notNull(jaxbContext, "Jaxb context must be present.");
    this.jaxbContext = jaxbContext;
    this.details = Optional.ofNullable(details)
        .orElseGet(JaxbContextDetails::empty);
  }

  @Override
  public Unmarshaller createUnmarshaller() throws JAXBException {
    Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
    if (!isEmpty(xmlAdapters)) {
      xmlAdapters.forEach(unmarshaller::setAdapter);
    }
    if (!isEmpty(attachmentUnmarshaller)) {
      unmarshaller.setAttachmentUnmarshaller(attachmentUnmarshaller);
    }
    if (!isEmpty(schema) && (schemaMode == SchemaMode.ALWAYS
        || schemaMode == SchemaMode.UNMARSHAL
        || schemaMode == SchemaMode.EXTERNAL_XSD)
        && !isEmpty(details.getSchemaLocation())) {
      unmarshaller.setSchema(schema);
    }
    if (!isEmpty(validationEventHandler)) {
      unmarshaller.setEventHandler(validationEventHandler);
    }
    return unmarshaller;
  }

  @Override
  public Marshaller createMarshaller() throws JAXBException {
    Marshaller marshaller = jaxbContext.createMarshaller();
    marshaller.setProperty(Marshaller.JAXB_ENCODING, StandardCharsets.UTF_8.name());
    marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, formattedOutput);
    if (!isEmpty(details.getSchemaLocation())) {
      marshaller.setProperty(Marshaller.JAXB_SCHEMA_LOCATION, details.getSchemaLocation());
    }
    if (!isEmpty(xmlAdapters)) {
      xmlAdapters.forEach(marshaller::setAdapter);
    }
    if (!isEmpty(attachmentMarshaller)) {
      marshaller.setAttachmentMarshaller(attachmentMarshaller);
    }
    if (!isEmpty(schema) && (schemaMode == SchemaMode.ALWAYS
        || schemaMode == SchemaMode.MARSHAL
        || schemaMode == SchemaMode.EXTERNAL_XSD)
        && !isEmpty(details.getSchemaLocation())) {
      marshaller.setSchema(schema);
    }
    if (!isEmpty(validationEventHandler)) {
      marshaller.setEventHandler(validationEventHandler);
    }
    return marshaller;
  }

  @Override
  public <T> Binder<T> createBinder(Class<T> domType) {
    return jaxbContext.createBinder(domType);
  }

  @Override
  public JAXBIntrospector createJAXBIntrospector() {
    return jaxbContext.createJAXBIntrospector();
  }

  @Override
  public void generateSchema(SchemaOutputResolver outputResolver) throws IOException {
    jaxbContext.generateSchema(outputResolver);
  }

}