BaseHelper.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.service.beans.utils;

  21. import java.io.InputStream;
  22. import java.lang.reflect.InvocationTargetException;
  23. import java.util.Iterator;
  24. import java.util.LinkedHashMap;
  25. import java.util.Map;
  26. import java.util.Optional;
  27. import java.util.Set;
  28. import java.util.function.Predicate;
  29. import java.util.regex.Matcher;
  30. import java.util.regex.Pattern;

  31. import javax.validation.Configuration;
  32. import javax.validation.ConstraintViolation;
  33. import javax.validation.Validation;
  34. import javax.validation.Validator;
  35. import javax.validation.ValidatorFactory;
  36. import javax.ws.rs.core.Response;

  37. import org.apache.commons.beanutils.BeanUtils;
  38. import org.apache.commons.beanutils.PropertyUtils;
  39. import org.apache.cxf.jaxrs.validation.JAXRSParameterNameProvider;
  40. import org.apache.cxf.validation.ValidationConfiguration;
  41. import org.openspcoop2.utils.Utilities;
  42. import org.openspcoop2.utils.io.Base64Utilities;
  43. import org.openspcoop2.utils.json.JSONUtils;
  44. import org.openspcoop2.utils.service.beans.ProfiloEnum;
  45. import org.openspcoop2.utils.service.fault.jaxrs.FaultCode;
  46. import org.openspcoop2.utils.service.fault.jaxrs.ProblemValidation;
  47. import org.openspcoop2.utils.transport.http.HttpConstants;

  48. /**
  49.  * Helper
  50.  *
  51.  * @author $Author$
  52.  * @version $Rev$, $Date$
  53.  *
  54.  */
  55. public class BaseHelper {
  56.    
  57.     @FunctionalInterface
  58.     public interface ThrowingSupplier<T> {
  59.        
  60.         T get() throws Exception;
  61.     }
  62.    
  63.     public interface ThrowingRunnable {
  64.        
  65.         void run() throws Exception;
  66.     }
  67.    
  68.     public static final<T> T dropnull(ThrowingSupplier<T> r) {
  69.         try {
  70.             return r.get();
  71.         } catch (NullPointerException e) {
  72.             return null;
  73.         } catch( Exception e) {
  74.             throw new RuntimeException(e);
  75.         }
  76.     }
  77.    
  78.     public static final <T> T evalnull(ThrowingSupplier<T> r) {
  79.         try {
  80.             return r.get();
  81.         } catch (Exception e) {
  82.             return null;
  83.         }
  84.     }
  85.    
  86.     public static final <T> T evalorElse(ThrowingSupplier<T> r, T orElse) {
  87.         T ret = null;
  88.         try {
  89.             ret = r.get();  
  90.         } catch (Exception e) {
  91.             // Ignoring Exception
  92.         }
  93.         if (ret != null) return ret;
  94.         else return orElse;
  95.     }
  96.        
  97.    
  98.     public static final <T> T supplyOrNotFound(ThrowingSupplier<T> s, String objName) {
  99.         T ret = null;
  100.         try {
  101.             ret = s.get();
  102.         } catch (Exception e) { }
  103.        
  104.         if (ret == null)
  105.             throw FaultCode.NOT_FOUND.toException(objName + " non presente nel registro.");
  106.        
  107.         return ret;
  108.     }
  109.    
  110.     public static final <T> T supplyOrNonValida(ThrowingSupplier<T> s, String objName) {
  111.         T ret = null;
  112.         try {
  113.             ret = s.get();
  114.         } catch (Exception e) { }
  115.        
  116.         if (ret == null)
  117.             throw FaultCode.RICHIESTA_NON_VALIDA.toException(objName + " non presente nel registro.");
  118.        
  119.         return ret;
  120.     }
  121.    
  122.    
  123.     public static final void runNull(ThrowingRunnable r) {
  124.         try {
  125.             r.run();
  126.         } catch (NullPointerException e) {
  127.             // Ignore
  128.         } catch ( Exception e) {
  129.             throw new RuntimeException(e);
  130.         }
  131.     }
  132.    
  133.     public static <T> Optional<T> findFirst(Iterable<? extends T> collection, Predicate<? super T> test) {
  134.         T value = null;
  135.         if (collection != null ) {
  136.             for (Iterator<? extends T> it = collection.iterator(); it.hasNext();)
  137.                 if (test.test(value = it.next())) {
  138.                     return Optional.of(value);
  139.                 }
  140.         }
  141.         return Optional.empty();
  142.     }
  143.    
  144.    

  145.     public static <T> T findAndRemoveFirst(Iterable<? extends T> collection, Predicate<? super T> test) {
  146.         T value = null;
  147.         for (Iterator<? extends T> it = collection.iterator(); it.hasNext();)
  148.             if (test.test(value = it.next())) {
  149.                 it.remove();
  150.                 return value;
  151.             }
  152.         return null;
  153.     }
  154.    
  155.    
  156.    
  157.     public static <T> void throwIfNull(T body) {
  158.         if (body == null)
  159.             throw FaultCode.RICHIESTA_NON_VALIDA.toException("Specificare un body");
  160.     }
  161.    
  162.    
  163.     /*@SuppressWarnings("unchecked")
  164.     public static <T extends Enum<T>> Object deserializeFromSwitch(Map<T,Class<?>> typeMap, T discr, Object body) throws UtilsException, InstantiationException, IllegalAccessException {
  165.         if (body == null) return null;

  166.         // TODO: Se tutto funziona, aggiungere eccezioni per discr non riconosciuto.
  167.         return fromMap((Map<String,Object>) body, typeMap.get(discr));
  168.     }*/
  169.    
  170.     /**
  171.      * Deserializza una Map json in un oggetto
  172.      * @throws IllegalAccessException
  173.      * @throws InstantiationException
  174.      */
  175.     public static final <T> T fromMap(Map<String,Object> mapObject, Class<T> toClass) throws InstantiationException, IllegalAccessException {
  176.         if (mapObject == null) return null;
  177.        
  178.         T ret = Utilities.newInstanceThrowInstantiationException(toClass);
  179.         fillFromMap(mapObject, ret);
  180.    
  181.         return ret;
  182.     }
  183.    
  184.     /*
  185.      * §Prende un nome in cui le parole sono separate da trattini bassi, e lo trasforma in stile
  186.      * upperCamelCase
  187.      */
  188.     public static final String jsonNameToUpperCC(String key) {
  189.        
  190.         StringBuilder sb = new StringBuilder();
  191.        
  192.         Matcher m = Pattern.compile("_(\\w)").matcher(key);
  193.        
  194.         while (m.find()) {
  195.             m.appendReplacement(sb, m.group(1).toUpperCase());
  196.         }
  197.         m.appendTail(sb);
  198.         return sb.toString();
  199.     }
  200.    
  201.     public static final <T> T fromJson(Object json, Class<T> c) {
  202.         if (json == null) return null;
  203.        
  204.         try {
  205.             return JSONUtils.getInstance().getAsObject(((InputStream)json), c);
  206.         } catch (Exception e) {
  207.             throw FaultCode.RICHIESTA_NON_VALIDA.toException(e);
  208.         }
  209.     }
  210.    
  211.     /**
  212.      * Questa funzione completa la deserializzazione di un oggetto jaxrs che arriva nella Api come una LinkedHashMap&lt;String, String or LinkedHashMap&lt;String, etc..&gt;&gt;
  213.      *
  214.      * Da notare che questo metodo sebbene generico per i fini di govway, non è da considerare un metodo valido di deserializzazione da una linkedHashmap, rappresentazione
  215.      * di un json, in un oggetto destinazione.
  216.      *
  217.      * @param mapObject
  218.      * @param toFill
  219.      */
  220.    
  221.     @SuppressWarnings({ "unchecked"})
  222.     public static final <T> void fillFromMap(Map<String,Object> mapObject, T toFill) throws InstantiationException {
  223.    
  224.         mapObject.forEach( (k, v) -> {
  225.             if (v == null) return;
  226.            
  227.             try {
  228.                 k = jsonNameToUpperCC(k);
  229.                 // La chiave k non è in upperCamelCase, segue lo  stile del json in cui tutto è minuscolo e i campi sono separati
  230.                 // da trattini bassi.
  231.                 Class<?> dest = PropertyUtils.getPropertyType(toFill, k);
  232.                 Class<?> source = v.getClass();

  233.                 if ( source == String.class ) {
  234.                     final String vs = (String) v;
  235.                    
  236.                     if ( dest == (new byte[0]).getClass() ) {                  
  237.                         BeanUtils.setProperty(toFill, k, Base64Utilities.decode(vs.getBytes()));
  238.                     }
  239.                     else if ( dest == String.class ) {
  240.                         BeanUtils.setProperty(toFill, k, vs);
  241.                     }
  242.                     else if ( dest.isEnum() ) {
  243.                         boolean found = false;
  244.                         Object[] constants = dest.getEnumConstants();
  245.                         if  (constants == null)
  246.                             throw new IllegalArgumentException("La classe passata non è un'enumerazione");
  247.                        
  248.                         for ( Object e : constants) {
  249.                             if (String.valueOf(e.toString()).equals(vs.trim())) {
  250.                                 found = true;
  251.                                 BeanUtils.setProperty(toFill, k, e);
  252.                                 break;
  253.                             }
  254.                         }
  255.                         if (!found)
  256.                             throw new IllegalArgumentException("Impossibile deserialzzare l'oggetto di valore: " + vs + " e classe destinazione " + dest.toGenericString());
  257.                     }
  258.                     else if ( dest == Integer.class ) {
  259.                         BeanUtils.setProperty(toFill, k, Integer.valueOf(vs));
  260.                     }
  261.                     else if (dest == Object.class ) {
  262.                         BeanUtils.setProperty(toFill, k, vs);
  263.                     }
  264.                     // TODO: Qui potrei sollevare un'eccezione se il campo non c'è nell'oggetto..
  265.                 }
  266.                
  267.                 else if ( source == LinkedHashMap.class && dest != Object.class) {  
  268.                         // Se dobbiamo deserializzare una Map, abbiamo bisogno che la destinazione sia tipata e non un semplice object.
  269.                         BeanUtils.setProperty(toFill, k, fromMap((Map<String,Object>)v, dest));
  270.                 }
  271.                 else {  // Fallback, li assumo dello stesso tipo, se non lo sono, ci pensa setProperty a sollevare eccezione.
  272.                     BeanUtils.setProperty(toFill, k, v);
  273.                 }
  274.                
  275.             } catch (IllegalAccessException e) {
  276.                 throw new RuntimeException(e);  
  277.             } catch (InvocationTargetException e) {
  278.                 throw new RuntimeException(e);  
  279.             } catch (NoSuchMethodException  e) {
  280.                 throw new RuntimeException(e);
  281.             } catch (InstantiationException e) {
  282.                 throw new RuntimeException(e);
  283.             }
  284.         });
  285.        
  286.     }
  287.    
  288.     public static final Map<ProfiloEnum,String> tipoProtocolloFromProfilo = ProfiloUtils.getMapProfiloToProtocollo();  
  289.     public static final Map<String,ProfiloEnum> profiloFromTipoProtocollo = ProfiloUtils.getMapProtocolloToProfilo();
  290.    

  291.     public static <T> void validateBean(T bean) {
  292.         if (bean == null) return;
  293.        
  294.         JAXRSParameterNameProvider parameterNameProvider = new JAXRSParameterNameProvider();
  295.         Configuration<?> factoryCfg = Validation.byDefaultProvider().configure();
  296.         ValidationConfiguration cfg = new ValidationConfiguration(parameterNameProvider);
  297.         if (cfg != null) {
  298.             factoryCfg.parameterNameProvider(cfg.getParameterNameProvider());
  299.             factoryCfg.messageInterpolator(cfg.getMessageInterpolator());
  300.             factoryCfg.traversableResolver(cfg.getTraversableResolver());
  301.             factoryCfg.constraintValidatorFactory(cfg.getConstraintValidatorFactory());
  302.             for (Map.Entry<String, String> entry : cfg.getProperties().entrySet()) {
  303.                 factoryCfg.addProperty(entry.getKey(), entry.getValue());
  304.             }
  305.         }

  306.         ValidatorFactory factory = factoryCfg.buildValidatorFactory();
  307.         Validator validator = factory.getValidator();
  308.         Set<ConstraintViolation<T>> violations = validator.validate(bean);
  309.        
  310.         if (!violations.isEmpty()) {
  311.             ProblemValidation problem = new ProblemValidation(FaultCode.RICHIESTA_NON_VALIDA.toFault());
  312.            
  313.             for (ConstraintViolation<T> violation : violations) {
  314.                 String msg = bean.getClass().getSimpleName() + "." + violation.getPropertyPath();
  315.                 problem.addInvalidParam(msg, violation.getMessage(), null);
  316.             }
  317.            
  318.             throw FaultCode.RICHIESTA_NON_VALIDA.toException(Response.status(problem.getStatus()).entity(problem).type(HttpConstants.CONTENT_TYPE_JSON_PROBLEM_DETAILS_RFC_7807).build());
  319.         }
  320.     }
  321.    
  322.     public static boolean validateAfterDeserialize = true;
  323.    
  324.     @SuppressWarnings("unchecked")
  325.     public static <T> T deserialize(Object o, Class<T> dest) {
  326.         T ret = null;
  327.        
  328.         if (o != null) {
  329.             if(dest.isInstance(o))
  330.             {
  331.                 ret = dest.cast(o);
  332.             }
  333.             else
  334.             {
  335.                 try {
  336.                     ret =  fromMap((Map<String, Object>) o,dest);
  337.                 } catch (Exception e) {
  338.                     throw FaultCode.RICHIESTA_NON_VALIDA.toException("Impossibile deserializzare l'oggetto " + dest.getName() + ", formato non valido: " + e.getMessage());
  339.                 }
  340.             }
  341.         }
  342.        
  343.         if(validateAfterDeserialize) {
  344.             validateBean(ret);
  345.         }
  346.         return ret;
  347.     }
  348.    
  349.    
  350.     public static <T> Optional<T> deserializeOptional(Object o, Class<T> dest) {
  351.         return Optional.ofNullable(deserialize(o,dest));
  352.     }
  353.    
  354.     public static <T> T deserializeDefault(Object o, Class<T> dest) {      
  355.         Optional<T> ret = deserializeOptional(o, dest);
  356.         if (
  357.                 (!ret.isPresent())
  358.                 ||
  359.                 (ret.isPresent() && ret.get() == null)
  360.         ) {
  361.             try {
  362.                 return Utilities.newInstance(dest);
  363.             } catch (Exception e) {
  364.                 throw new RuntimeException(e);
  365.             }
  366.         } else {
  367.             return ret.get();
  368.         }      
  369.     }
  370.    
  371.    
  372. }