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

com.hcl.domino.jnx.vertx.json.service.VertxJsonSerializer Maven / Gradle / Ivy

The newest version!
/*
 * ==========================================================================
 * Copyright (C) 2019-2022 HCL America, Inc. ( http://www.hcl.com/ )
 *                            All rights reserved.
 * ==========================================================================
 * 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 .
 *
 * 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 com.hcl.domino.jnx.vertx.json.service;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Formatter;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;

import org.apache.commons.io.IOUtils;

import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.hcl.domino.DominoException;
import com.hcl.domino.commons.design.DefaultActionBar;
import com.hcl.domino.commons.design.action.DefaultActionBarAction;
import com.hcl.domino.commons.json.AbstractJsonSerializer;
import com.hcl.domino.commons.json.JsonUtil;
import com.hcl.domino.data.Document;
import com.hcl.domino.data.DocumentClass;
import com.hcl.domino.data.DominoDateRange;
import com.hcl.domino.data.DominoDateTime;
import com.hcl.domino.data.DominoOriginatorId;
import com.hcl.domino.data.DominoTimeType;
import com.hcl.domino.data.ItemDataType;
import com.hcl.domino.design.DesignAgent;
import com.hcl.domino.design.DesignElement;
import com.hcl.domino.design.Folder;
import com.hcl.domino.design.GenericFormOrSubform;
import com.hcl.domino.design.JavaAgentOrLibrary;
import com.hcl.domino.design.Outline;
import com.hcl.domino.design.View;
import com.hcl.domino.design.agent.DesignImportedJavaAgent;
import com.hcl.domino.exception.EntryNotFoundInIndexException;
import com.hcl.domino.exception.ItemNotFoundException;
import com.hcl.domino.html.HtmlConversionResult;
import com.hcl.domino.html.HtmlConvertOption;
import com.hcl.domino.html.RichTextHTMLConverter;
import com.hcl.domino.jnx.vertx.json.DefaultActionBarActionMixIn;
import com.hcl.domino.jnx.vertx.json.DefaultActionBarMixIn;
import com.hcl.domino.jnx.vertx.json.DesignAgentMixIn;
import com.hcl.domino.jnx.vertx.json.DesignElementMixIn;
import com.hcl.domino.jnx.vertx.json.FolderMixIn;
import com.hcl.domino.jnx.vertx.json.GenericFormOrSubformMixIn;
import com.hcl.domino.jnx.vertx.json.ImportedJavaAgentContentMixIn;
import com.hcl.domino.jnx.vertx.json.JavaAgentOrLibraryContentMixIn;
import com.hcl.domino.jnx.vertx.json.JnxTypesModule;
import com.hcl.domino.jnx.vertx.json.MemoryStructureMixIn;
import com.hcl.domino.jnx.vertx.json.OutlineMixIn;
import com.hcl.domino.jnx.vertx.json.ResizableMemoryStructureMixIn;
import com.hcl.domino.jnx.vertx.json.RichTextRecordMixIn;
import com.hcl.domino.jnx.vertx.json.ViewMixIn;
import com.hcl.domino.json.JsonSerializer;
import com.hcl.domino.mime.MimeReader.ReadMimeDataType;
import com.hcl.domino.richtext.records.RichTextRecord;
import com.hcl.domino.richtext.structures.MemoryStructure;
import com.hcl.domino.richtext.structures.ResizableMemoryStructure;

import io.vertx.core.json.JsonObject;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;

public class VertxJsonSerializer extends AbstractJsonSerializer {
  
  public VertxJsonSerializer() {
    //add custom mixin classes for json serialization
    io.vertx.core.json.jackson.DatabindCodec.prettyMapper().setSerializationInclusion(Include.NON_EMPTY);
    io.vertx.core.json.jackson.DatabindCodec.mapper().configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
    io.vertx.core.json.jackson.DatabindCodec.mapper().addMixIn(DesignElement.class, DesignElementMixIn.class);
    io.vertx.core.json.jackson.DatabindCodec.mapper().addMixIn(Outline.class, OutlineMixIn.class);
    io.vertx.core.json.jackson.DatabindCodec.mapper().addMixIn(View.class, ViewMixIn.class);
    io.vertx.core.json.jackson.DatabindCodec.mapper().addMixIn(Folder.class, FolderMixIn.class);
    io.vertx.core.json.jackson.DatabindCodec.mapper().addMixIn(GenericFormOrSubform.class, GenericFormOrSubformMixIn.class);
    io.vertx.core.json.jackson.DatabindCodec.mapper().addMixIn(MemoryStructure.class, MemoryStructureMixIn.class);
    io.vertx.core.json.jackson.DatabindCodec.mapper().addMixIn(RichTextRecord.class, RichTextRecordMixIn.class);
    io.vertx.core.json.jackson.DatabindCodec.mapper().addMixIn(ResizableMemoryStructure.class, ResizableMemoryStructureMixIn.class);
    io.vertx.core.json.jackson.DatabindCodec.mapper().addMixIn(DefaultActionBar.class, DefaultActionBarMixIn.class);
    io.vertx.core.json.jackson.DatabindCodec.mapper().addMixIn(DefaultActionBarAction.class, DefaultActionBarActionMixIn.class);
    io.vertx.core.json.jackson.DatabindCodec.mapper().addMixIn(JavaAgentOrLibrary.class, JavaAgentOrLibraryContentMixIn.class);
    io.vertx.core.json.jackson.DatabindCodec.mapper().addMixIn(DesignAgent.class, DesignAgentMixIn.class);
    io.vertx.core.json.jackson.DatabindCodec.mapper().addMixIn(DesignImportedJavaAgent.class, ImportedJavaAgentContentMixIn.class);
    io.vertx.core.json.jackson.DatabindCodec.mapper().registerModule(new Jdk8Module());
    io.vertx.core.json.jackson.DatabindCodec.mapper().registerModule(new JavaTimeModule());
    io.vertx.core.json.jackson.DatabindCodec.mapper().registerModule(new JnxTypesModule());
  }

  /**
   * Json serialization of Objects with vertx ObjectMapper
   *
   * @since 1.0.32
   */
  @Override
  public JsonObject toJson(final Object value) {
	  return JsonObject.mapFrom(value);
  }
  
  @Override
  public JsonObject toJson(final Document doc) {
    final JsonObject result = new JsonObject();

    final Set handledItems = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
    handledItems.addAll(JsonSerializer.DEFAULT_EXCLUDED_ITEMS);
    if (this.includedItemNames != null) {
      handledItems.removeAll(this.includedItemNames);
    }
    if (this.skippedItemNames != null) {
      handledItems.addAll(this.skippedItemNames);
    }

    if (this.includeMetadata) {
      final JsonObject meta = new JsonObject();
      result.put(JsonSerializer.PROP_METADATA, meta);
      meta.put(JsonSerializer.PROP_META_NOTEID, doc.getNoteID());
      meta.put(JsonSerializer.PROP_META_UNID, doc.getUNID());
      meta.put(JsonSerializer.PROP_META_CREATED, JsonUtil.toIsoString(doc.getCreated()));
      meta.put(JsonSerializer.PROP_META_LASTMODIFIED, JsonUtil.toIsoString(doc.getLastModified()));
      meta.put(JsonSerializer.PROP_META_LASTACCESSED, JsonUtil.toIsoString(doc.getLastAccessed()));
      meta.put(JsonSerializer.PROP_META_LASTMODIFIEDINFILE, JsonUtil.toIsoString(doc.getModifiedInThisFile()));
      meta.put(JsonSerializer.PROP_META_ADDEDTOFILE, JsonUtil.toIsoString(doc.getAddedToFile()));
      meta.put(JsonSerializer.PROP_META_NOTECLASS,
          doc.getDocumentClass().stream()
              .map(DocumentClass::name)
              .collect(Collectors.toList()));
      meta.put(JsonSerializer.PROP_META_UNREAD, doc.isUnread());
      meta.put(JsonSerializer.PROP_META_EDITABLE, doc.isEditable());
      {
        DominoOriginatorId oid = doc.getOID();
        int[] seqTime = oid.getSequenceTime().getAdapter(int[].class);
        
        try(Formatter formatter = new Formatter()) {
          formatter.format("%08x", oid.getSequence()); //$NON-NLS-1$
          formatter.format("%08x", seqTime[0]); //$NON-NLS-1$
          formatter.format("%08x", seqTime[1]); //$NON-NLS-1$
          meta.put(JsonSerializer.PROP_META_REVISION, formatter.toString().toUpperCase());
        }
      }
      
      if(doc.isResponse()) {
        meta.put(JsonSerializer.PROP_META_PARENTUNID, doc.getParentDocumentUNID());
      }
      Optional threadId = doc.getThreadID();
      if(threadId.isPresent()) {
        meta.put(JsonSerializer.PROP_META_THREADID, threadId.get());
      }

      
      if(doc.isSigned()) {
        String signer;
        try {
          signer = doc.getSigner();
        } catch(DominoException e) {
          // Likely in many cases - no cross cert, mismatched pub key, etc.
          signer = e.getLocalizedMessage();
        }
        meta.put(JsonSerializer.PROP_META_SIGNER, signer);
      }
    }

    if(!this.metaOnly) {
      doc.forEachItem((item, loop) -> {
        final String itemName = item.getName();
        if (itemName != null && !handledItems.contains(itemName)) {
          handledItems.add(itemName);
  
          if (this.includedItemNames != null && !this.includedItemNames.contains(itemName)) {
            // Skip
            return;
          }
  
          final ItemDataType type = item.getType();
          if ((this.excludedTypes != null && this.excludedTypes.contains(type)) || AbstractJsonSerializer.isExcludedField(itemName)) {
            return;
          }
  
          final String propName = this.lowercaseProperties ? itemName.toLowerCase() : itemName;
  
          if (this.customProcessors.containsKey(itemName)) {
            result.put(propName, this.customProcessors.get(itemName).apply(doc, itemName));
            return;
          }
  
          switch (type) {
            case TYPE_NUMBER: {
              final double value = item.get(double.class, 0d);
              if (this.booleanItemNames.contains(propName)) {
                final boolean val = AbstractJsonSerializer.matchesBooleanValues(value, this.booleanTrueValues);
                result.put(propName, val);
              } else {
                result.put(propName, value);
              }
              break;
            }
            case TYPE_NUMBER_RANGE: {
              final List vals = item.getAsList(Double.class, Collections.emptyList());
              if (this.booleanItemNames.contains(propName)) {
                if (vals.size() == 1) {
                  final boolean val = AbstractJsonSerializer.matchesBooleanValues(vals.get(0), this.booleanTrueValues);
                  result.put(propName, val);
                } else {
                  result.put(propName, false);
                }
              } else {
                result.put(propName, vals);
              }
              break;
            }
            case TYPE_RFC822_TEXT:
            case TYPE_TEXT: {
              final String val = item.get(String.class, null);
              if (this.booleanItemNames.contains(propName)) {
                final boolean boolVal = AbstractJsonSerializer.matchesBooleanValues(val, this.booleanTrueValues);
                result.put(propName, boolVal);
              } else {
                if (val == null) {
                  result.putNull(propName);
                } else {
                  result.put(propName, val);
                }
              }
              break;
            }
            case TYPE_TEXT_LIST: {
              final List vals = item.getAsList(String.class, Collections.emptyList());
              if (this.booleanItemNames.contains(propName)) {
                if (vals.size() == 1) {
                  final boolean val = AbstractJsonSerializer.matchesBooleanValues(vals.get(0), this.booleanTrueValues);
                  result.put(propName, val);
                } else {
                  result.put(propName, false);
                }
              } else {
                result.put(propName, vals);
              }
              break;
            }
            case TYPE_TIME: {
              final DominoTimeType val = item.get(DominoTimeType.class, null);
              this.writeTimeProperty(result, propName, val);
              break;
            }
            case TYPE_TIME_RANGE: {
              final List vals = item.getAsList(DominoTimeType.class, Collections.emptyList());
              if (vals.size() == 1) {
                this.writeTimeProperty(result, propName, vals.get(0));
              } else {
                result.put(propName,
                    vals.stream()
                        .map(dt -> {
                          if (dt instanceof DominoDateTime) {
                            return JsonUtil.toIsoString((DominoDateTime) dt);
                          } else {
                            switch (this.dateRangeFormat) {
                              case OBJECT:
                                final JsonObject inner = new JsonObject();
                                inner.put(JsonSerializer.PROP_RANGE_FROM, ((DominoDateRange) dt).getStartDateTime());
                                inner.put(JsonSerializer.PROP_RANGE_TO, ((DominoDateRange) dt).getEndDateTime());
                                return result;
                              case ISO:
                              default:
                                return JsonUtil.toIsoString((DominoDateRange) dt);
                            }
                          }
                        })
                        .collect(Collectors.toList()));
              }
              break;
            }
            case TYPE_COMPOSITE:
              try {
                final RichTextHTMLConverter.Builder builder = doc.getParentDatabase()
                    .getParentDominoClient()
                    .getRichTextHtmlConverter()
                    .renderItem(doc, propName);
                if (this.htmlConvertOptions.isEmpty()) {
                  builder.option(HtmlConvertOption.XMLCompatibleHTML, "1"); //$NON-NLS-1$
                } else {
                  this.htmlConvertOptions.forEach(builder::option);
                }
                final HtmlConversionResult conv = builder.convert();
                result.put(propName, conv.getHtml());
              } catch (ItemNotFoundException | EntryNotFoundInIndexException e) {
                // Occurs with design notes
                result.put(propName, ""); //$NON-NLS-1$
              } catch (final DominoException e) {
                switch (e.getId()) {
                  case 14941:
                  case 14944:
                    // Un-messaged error codes observed with design notes
                    result.put(propName, ""); //$NON-NLS-1$
                    break;
                  default:
                    throw e;
                }
              }
              break;
            case TYPE_MIME_PART:
              // TODO read inline?
              // TODO rationalize multiple body types
              final MimeMessage mime = doc.getParentDatabase()
                  .getParentDominoClient()
                  .getMimeReader()
                  .readMIME(doc, propName, EnumSet.of(ReadMimeDataType.MIMEHEADERS));
              String content;
              try (InputStream is = mime.getInputStream()) {
                content = IOUtils.toString(is, StandardCharsets.UTF_8);
              } catch (IOException | MessagingException e) {
                throw new RuntimeException(e);
              }
              if (content == null) {
                result.putNull(propName);
              } else {
                result.put(propName, content.toString());
              }
              break;
            case TYPE_HTML:
              // TODO this is probably a specialized value, but the underlying API could
              // handle converting to string
              break;
            case TYPE_USERDATA:
              // TODO Base64? Custom adapters?
              break;
            case TYPE_FORMULA:
            case TYPE_ERROR:
            case TYPE_NOTEREF_LIST:
              // TODO convert to string?
              break;
            case TYPE_ACTION:
            case TYPE_ASSISTANT_INFO:
            case TYPE_CALENDAR_FORMAT:
            case TYPE_COLLATION:
            case TYPE_HIGHLIGHTS:
            case TYPE_ICON:
            case TYPE_INVALID_OR_UNKNOWN:
            case TYPE_LSOBJECT:
            case TYPE_NOTELINK_LIST:
            case TYPE_OBJECT:
            case TYPE_QUERY:
            case TYPE_SCHED_LIST:
            case TYPE_SEAL:
            case TYPE_SEAL2:
            case TYPE_SEALDATA:
            case TYPE_SEAL_LIST:
            case TYPE_SIGNATURE:
            case TYPE_UNAVAILABLE:
            case TYPE_USERID:
            case TYPE_VIEWMAP_DATASET:
            case TYPE_VIEWMAP_LAYOUT:
            case TYPE_VIEW_FORMAT:
            case TYPE_WORKSHEET_DATA:
            default:
              break;
  
          }
        }
      });
    }

    return result;
  }

  private void writeTimeProperty(final JsonObject result, final String propName, final DominoTimeType val) {
    if (val == null) {
      result.putNull(propName);
    } else {
      if (val instanceof DominoDateTime) {
        result.put(propName, JsonUtil.toIsoString((DominoDateTime) val));
      } else {
        switch (this.dateRangeFormat) {
          case OBJECT:
            final JsonObject inner = new JsonObject();
            inner.put(JsonSerializer.PROP_RANGE_FROM, JsonUtil.toIsoString(((DominoDateRange) val).getStartDateTime()));
            inner.put(JsonSerializer.PROP_RANGE_TO, JsonUtil.toIsoString(((DominoDateRange) val).getEndDateTime()));
            result.put(propName, inner);
            break;
          case ISO:
          default:
            result.put(propName, JsonUtil.toIsoString((DominoDateRange) val));
        }
      }
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy