YAMLUtils.java

  1. /*
  2.  * GovWay - A customizable API Gateway
  3.  * https://govway.org
  4.  *
  5.  * Copyright (c) 2005-2025 Link.it srl (https://link.it).
  6.  *
  7.  * This program is free software: you can redistribute it and/or modify
  8.  * it under the terms of the GNU General Public License version 3, as published by
  9.  * the Free Software Foundation.
  10.  *
  11.  * This program is distributed in the hope that it will be useful,
  12.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14.  * GNU General Public License for more details.
  15.  *
  16.  * You should have received a copy of the GNU General Public License
  17.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  18.  *
  19.  */

  20. package org.openspcoop2.utils.json;

  21. import java.io.Serializable;
  22. import java.util.HashMap;
  23. import java.util.List;
  24. import java.util.Map;
  25. import java.util.TimeZone;
  26. import java.util.regex.Matcher;
  27. import java.util.regex.Pattern;

  28. import org.openspcoop2.utils.SemaphoreLock;
  29. import org.openspcoop2.utils.UtilsException;
  30. import org.slf4j.Logger;

  31. import com.fasterxml.jackson.annotation.JsonInclude.Include;
  32. import com.fasterxml.jackson.databind.ObjectMapper;
  33. import com.fasterxml.jackson.databind.ObjectWriter;
  34. import com.fasterxml.jackson.databind.SerializationFeature;
  35. import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
  36. import com.fasterxml.jackson.datatype.joda.JodaModule;
  37. import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;

  38. /**
  39.  * YAMLUtils
  40.  *
  41.  * @author Poli Andrea (apoli@link.it)
  42.  * @author $Author$
  43.  * @version $Rev$, $Date$
  44.  */
  45. public class YAMLUtils extends AbstractUtils {
  46.    
  47.     static {
  48.         YamlSnakeLimits.initialize();
  49.     }
  50.    
  51.     private static YAMLUtils yamlUtils = null;
  52.     private static YAMLUtils yamlUtilsPretty = null;
  53.     private static synchronized void init(boolean prettyPrint){
  54.         if(prettyPrint) {
  55.             if(YAMLUtils.yamlUtilsPretty==null){
  56.                 YAMLUtils.yamlUtilsPretty = new YAMLUtils(true);
  57.             }
  58.         }
  59.         else {
  60.             if(YAMLUtils.yamlUtils==null){
  61.                 YAMLUtils.yamlUtils = new YAMLUtils(false);
  62.             }
  63.         }
  64.     }
  65.     public static YAMLUtils getInstance(){
  66.         return getInstance(false);
  67.     }
  68.     public static YAMLUtils getInstance(boolean prettyPrint){
  69.         if(prettyPrint) {
  70.             if(YAMLUtils.yamlUtilsPretty==null){
  71.                 // spotbugs warning 'SING_SINGLETON_GETTER_NOT_SYNCHRONIZED'
  72.                 synchronized (YAMLUtils.class) {
  73.                     YAMLUtils.init(true);
  74.                 }
  75.             }
  76.             return YAMLUtils.yamlUtilsPretty;
  77.         }
  78.         else {
  79.             if(YAMLUtils.yamlUtils==null){
  80.                 // spotbugs warning 'SING_SINGLETON_GETTER_NOT_SYNCHRONIZED'
  81.                 synchronized (YAMLUtils.class) {
  82.                     YAMLUtils.init(false);
  83.                 }
  84.             }
  85.             return YAMLUtils.yamlUtils;
  86.         }
  87.     }
  88.    

  89.     private static org.openspcoop2.utils.Semaphore semaphore = new org.openspcoop2.utils.Semaphore("JSONUtils");
  90.     private static YAMLMapper internalMapper;
  91.     private static synchronized void initMapper()  {
  92.         SemaphoreLock lock = semaphore.acquireThrowRuntime("initMapper");
  93.         try {
  94.             if(internalMapper==null){
  95.                 internalMapper = new YAMLMapper();
  96.                 internalMapper.setTimeZone(TimeZone.getDefault());
  97.                 internalMapper.setSerializationInclusion(Include.NON_NULL);
  98.                 internalMapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
  99.                 internalMapper.configure(com.fasterxml.jackson.databind.SerializationFeature.
  100.                         WRITE_DATES_AS_TIMESTAMPS , false);
  101.                 // Since 2.1.4, the field exampleSetFlag appears in json produced with ObjectMapper#writeValue(File, Object) when serializing an object of type OpenApi.
  102.                 // "exampleSetFlag" : false
  103.                 // Con il codice sottostante, nelle classi Mixin l'attributo 'getExampleSetFlag' viene ignorato
  104.                 internalMapper.addMixIn(io.swagger.v3.oas.models.media.Schema.class, io.swagger.v3.core.jackson.mixin.SchemaMixin.class);
  105.                 internalMapper.addMixIn(io.swagger.v3.oas.models.media.MediaType.class, io.swagger.v3.core.jackson.mixin.MediaTypeMixin.class);
  106.             }
  107.         }finally {
  108.             semaphore.release(lock, "initMapper");
  109.         }
  110.     }
  111.     public static void setMapperTimeZone(TimeZone timeZone) {
  112.         if(internalMapper==null){
  113.             initMapper();
  114.         }
  115.         SemaphoreLock lock = semaphore.acquireThrowRuntime("setMapperTimeZone");
  116.         try {
  117.             internalMapper.setTimeZone(timeZone);
  118.         }finally {
  119.             semaphore.release(lock, "setMapperTimeZone");
  120.         }
  121.     }
  122.     public static void registerJodaModule() {
  123.         if(internalMapper==null){
  124.             initMapper();
  125.         }
  126.         SemaphoreLock lock = semaphore.acquireThrowRuntime("registerJodaModule");
  127.         try {
  128.             internalMapper.registerModule(new JodaModule());
  129.         }finally {
  130.             semaphore.release(lock, "registerJodaModule");
  131.         }
  132.     }
  133.     public static void registerJavaTimeModule() {
  134.         if(internalMapper==null){
  135.             initMapper();
  136.         }
  137.         SemaphoreLock lock = semaphore.acquireThrowRuntime("registerJavaTimeModule");
  138.         try {
  139.             internalMapper.registerModule(new JavaTimeModule());
  140.         }finally {
  141.             semaphore.release(lock, "registerJavaTimeModule");
  142.         }
  143.     }
  144.    
  145.     public static YAMLMapper getObjectMapper() {
  146.         if(internalMapper==null){
  147.             initMapper();
  148.         }
  149.         return internalMapper;
  150.     }
  151.    
  152.     private static ObjectWriter writer;
  153.     private static synchronized void initWriter()  {
  154.         if(internalMapper==null){
  155.             initMapper();
  156.         }
  157.         if(writer==null){
  158.             writer = internalMapper.writer();
  159.         }
  160.     }
  161.     public static ObjectWriter getObjectWriter() {
  162.         if(writer==null){
  163.             initWriter();
  164.         }
  165.         return writer;
  166.     }
  167.    
  168.     private static ObjectWriter writerPrettyPrint;
  169.     private static synchronized void initWriterPrettyPrint()  {
  170.         if(internalMapper==null){
  171.             initMapper();
  172.         }
  173.         if(writerPrettyPrint==null){
  174.             writerPrettyPrint = internalMapper.writer().withDefaultPrettyPrinter();
  175.         }
  176.     }
  177.     public static ObjectWriter getObjectWriterPrettyPrint() {
  178.         if(writerPrettyPrint==null){
  179.             initWriterPrettyPrint();
  180.         }
  181.         return writerPrettyPrint;
  182.     }
  183.    
  184.    
  185.    
  186.     private YAMLUtils(boolean prettyPrint) {
  187.         super(prettyPrint);
  188.     }
  189.    
  190.     @Override
  191.     protected void _initMapper() {
  192.         initMapper();
  193.     }
  194.     @Override
  195.     protected void _initWriter(boolean prettyPrint) {
  196.         if(prettyPrint) {
  197.             initWriterPrettyPrint();
  198.         }
  199.         else {
  200.             initWriter();
  201.         }
  202.     }
  203.    
  204.     @Override
  205.     protected ObjectMapper _getObjectMapper() {
  206.         return getObjectMapper();
  207.     }
  208.     @Override
  209.     protected ObjectWriter _getObjectWriter(boolean prettyPrint) {
  210.         if(prettyPrint) {
  211.             return getObjectWriterPrettyPrint();
  212.         }
  213.         else {
  214.             return getObjectWriter();
  215.         }
  216.     }
  217.    
  218.    
  219.     // IS
  220.    
  221.     public boolean isYaml(byte[]jsonBytes){
  222.         return !JSONUtils.getInstance().isJson(jsonBytes) && this.isValid(jsonBytes);
  223.     }
  224.    
  225.     public boolean isYaml(String jsonString){
  226.         return !JSONUtils.getInstance().isJson(jsonString) && this.isValid(jsonString);
  227.     }
  228.    
  229.    
  230.     // UTILS per ANCHOR
  231.    
  232.     public static boolean containsKeyAnchor(String yaml) {
  233.         return containsMergeKeyAnchor(yaml) || containsRefKeyAnchor(yaml);
  234.     }
  235.     public static boolean containsMergeKeyAnchor(String yaml) {
  236.         return yaml!=null && yaml.contains("<<: *");
  237.     }
  238.     public static boolean containsRefKeyAnchor(String yaml) {
  239.         return yaml!=null && yaml.contains(" *") && yaml.contains(" &");
  240.     }
  241.    
  242.     public static String resolveMergeKeyAndConvertToJson(String yaml) throws UtilsException {
  243.         return resolveMergeKeyAndConvertToJson(yaml, JSONUtils.getInstance());
  244.     }
  245.     public static String resolveMergeKeyAndConvertToJson(String yaml, JSONUtils jsonUtils) throws UtilsException {
  246.         return  resolveAnchorAndConvertToJson(yaml, jsonUtils, false);
  247.     }
  248.     public static String resolveKeyAnchorAndConvertToJson(String yaml) throws UtilsException {
  249.         return resolveKeyAnchorAndConvertToJson(yaml, JSONUtils.getInstance());
  250.     }
  251.     public static String resolveKeyAnchorAndConvertToJson(String yaml, JSONUtils jsonUtils) throws UtilsException {
  252.         return  resolveAnchorAndConvertToJson(yaml, jsonUtils, true);
  253.     }
  254.     private static String resolveAnchorAndConvertToJson(String yaml, JSONUtils jsonUtils, boolean resolveAllAnchor) throws UtilsException {
  255.         // Fix merge key '<<: *'
  256.         // La funzionalità di merge key è supportata fino allo yaml 1.1 (https://ktomk.github.io/writing/yaml-anchor-alias-and-merge-key.html)
  257.         // Mentre OpenAPI dice di usare preferibilmente YAML 1.2 (https://swagger.io/specification/):
  258.         //   "n order to preserve the ability to round-trip between YAML and JSON formats, YAML version 1.2 is RECOMMENDED"
  259.         // Inoltre le anchor utilizzate nelle merge key non sono supportate correttamente in jackson:
  260.         //   https://github.com/FasterXML/jackson-dataformats-text/issues/98
  261.         // Mentre vengono gestite correttamente da snake (https://linuxtut.com/convert-json-and-yaml-in-java-(using-jackson-and-snakeyaml)-0ad0a/)
  262.         // Come fix quindi nel caso siano presenti viene fatta una serializzazione tramite snake che le risolve.
  263.         boolean contains = resolveAllAnchor ? containsKeyAnchor(yaml) : containsMergeKeyAnchor(yaml);
  264.         if(contains) {
  265.             // Risoluzione merge key '<<: *'
  266.             Map<String, Object> obj = new org.yaml.snakeyaml.Yaml().load(yaml);
  267.             /**System.out.println("COSTRUITO ["+jsonUtils.toString(obj)+"]");*/
  268.             return jsonUtils.toString(obj); // jsonRepresentation
  269.         }
  270.         return null;
  271.     }
  272.    
  273.    
  274.     /** UTILS per tipi vuoi schema: {} */
  275.    
  276.     // Fix: 1620
  277.     public static boolean containsEmptySchema(String yaml) {
  278.         String pattern = "(?m)^([ \\t]*)schema\\s*:\\s*\\{\\s*\\}\\s*$";
  279.         try {
  280.             Pattern p = Pattern.compile(pattern);
  281.             Matcher matcher = p.matcher(yaml);
  282.             return matcher.find();
  283.         }catch(Exception e) {
  284.             return false;
  285.         }
  286.     }
  287.     public static String resolveEmptySchema(String yaml) {
  288.          // Regex: trova righe con "schema: {}" e sostituisce con due righe correttamente indentate
  289.         return yaml.replaceAll("(?m)^([ \\t]*)schema:\\s*\\{\\s*\\}", "$1schema:\n$1  type: string");
  290.     }
  291.    
  292.     // CONVERT TO MAP
  293.    
  294.     public Map<String, Serializable> convertToMap(Logger log, String source, String raw) {
  295.         return this.convertToMap(log, source, raw, null);
  296.     }
  297.     public Map<String, Serializable> convertToMap(Logger log, String source, String raw, List<String> claimsToConvert) {
  298.         if(this.isYaml(raw)) {
  299.             return super.convertToMapEngine(log, source, raw, claimsToConvert);
  300.         }
  301.         else {
  302.             return new HashMap<>(); // empty return
  303.         }  
  304.     }
  305.    
  306.     public Map<String, Serializable> convertToMap(Logger log, String source, byte[]raw) {
  307.         return this.convertToMap(log, source, raw, null);
  308.     }
  309.     public Map<String, Serializable> convertToMap(Logger log, String source, byte[]raw, List<String> claimsToConvert) {
  310.         if(this.isYaml(raw)) {
  311.             return super.convertToMapEngine(log, source, raw, claimsToConvert);
  312.         }
  313.         else {
  314.             return new HashMap<>(); // empty return
  315.         }  
  316.     }
  317. }