BaseHelper.java
/*
* GovWay - A customizable API Gateway
* https://govway.org
*
* Copyright (c) 2005-2024 Link.it srl (https://link.it).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3, as published by
* the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.openspcoop2.utils.service.beans.utils;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.validation.Configuration;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.ws.rs.core.Response;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.cxf.jaxrs.validation.JAXRSParameterNameProvider;
import org.apache.cxf.validation.ValidationConfiguration;
import org.openspcoop2.utils.Utilities;
import org.openspcoop2.utils.io.Base64Utilities;
import org.openspcoop2.utils.json.JSONUtils;
import org.openspcoop2.utils.service.beans.ProfiloEnum;
import org.openspcoop2.utils.service.fault.jaxrs.FaultCode;
import org.openspcoop2.utils.service.fault.jaxrs.ProblemValidation;
import org.openspcoop2.utils.transport.http.HttpConstants;
/**
* Helper
*
* @author $Author$
* @version $Rev$, $Date$
*
*/
public class BaseHelper {
@FunctionalInterface
public interface ThrowingSupplier<T> {
T get() throws Exception;
}
public interface ThrowingRunnable {
void run() throws Exception;
}
public static final<T> T dropnull(ThrowingSupplier<T> r) {
try {
return r.get();
} catch (NullPointerException e) {
return null;
} catch( Exception e) {
throw new RuntimeException(e);
}
}
public static final <T> T evalnull(ThrowingSupplier<T> r) {
try {
return r.get();
} catch (Exception e) {
return null;
}
}
public static final <T> T evalorElse(ThrowingSupplier<T> r, T orElse) {
T ret = null;
try {
ret = r.get();
} catch (Exception e) {
// Ignoring Exception
}
if (ret != null) return ret;
else return orElse;
}
public static final <T> T supplyOrNotFound(ThrowingSupplier<T> s, String objName) {
T ret = null;
try {
ret = s.get();
} catch (Exception e) { }
if (ret == null)
throw FaultCode.NOT_FOUND.toException(objName + " non presente nel registro.");
return ret;
}
public static final <T> T supplyOrNonValida(ThrowingSupplier<T> s, String objName) {
T ret = null;
try {
ret = s.get();
} catch (Exception e) { }
if (ret == null)
throw FaultCode.RICHIESTA_NON_VALIDA.toException(objName + " non presente nel registro.");
return ret;
}
public static final void runNull(ThrowingRunnable r) {
try {
r.run();
} catch (NullPointerException e) {
// Ignore
} catch ( Exception e) {
throw new RuntimeException(e);
}
}
public static <T> Optional<T> findFirst(Iterable<? extends T> collection, Predicate<? super T> test) {
T value = null;
if (collection != null ) {
for (Iterator<? extends T> it = collection.iterator(); it.hasNext();)
if (test.test(value = it.next())) {
return Optional.of(value);
}
}
return Optional.empty();
}
public static <T> T findAndRemoveFirst(Iterable<? extends T> collection, Predicate<? super T> test) {
T value = null;
for (Iterator<? extends T> it = collection.iterator(); it.hasNext();)
if (test.test(value = it.next())) {
it.remove();
return value;
}
return null;
}
public static <T> void throwIfNull(T body) {
if (body == null)
throw FaultCode.RICHIESTA_NON_VALIDA.toException("Specificare un body");
}
/*@SuppressWarnings("unchecked")
public static <T extends Enum<T>> Object deserializeFromSwitch(Map<T,Class<?>> typeMap, T discr, Object body) throws UtilsException, InstantiationException, IllegalAccessException {
if (body == null) return null;
// TODO: Se tutto funziona, aggiungere eccezioni per discr non riconosciuto.
return fromMap((Map<String,Object>) body, typeMap.get(discr));
}*/
/**
* Deserializza una Map json in un oggetto
* @throws IllegalAccessException
* @throws InstantiationException
*/
public static final <T> T fromMap(Map<String,Object> mapObject, Class<T> toClass) throws InstantiationException, IllegalAccessException {
if (mapObject == null) return null;
T ret = Utilities.newInstanceThrowInstantiationException(toClass);
fillFromMap(mapObject, ret);
return ret;
}
/*
* §Prende un nome in cui le parole sono separate da trattini bassi, e lo trasforma in stile
* upperCamelCase
*/
public static final String jsonNameToUpperCC(String key) {
StringBuilder sb = new StringBuilder();
Matcher m = Pattern.compile("_(\\w)").matcher(key);
while (m.find()) {
m.appendReplacement(sb, m.group(1).toUpperCase());
}
m.appendTail(sb);
return sb.toString();
}
public static final <T> T fromJson(Object json, Class<T> c) {
if (json == null) return null;
try {
return JSONUtils.getInstance().getAsObject(((InputStream)json), c);
} catch (Exception e) {
throw FaultCode.RICHIESTA_NON_VALIDA.toException(e);
}
}
/**
* Questa funzione completa la deserializzazione di un oggetto jaxrs che arriva nella Api come una LinkedHashMap<String, String or LinkedHashMap<String, etc..>>
*
* Da notare che questo metodo sebbene generico per i fini di govway, non è da considerare un metodo valido di deserializzazione da una linkedHashmap, rappresentazione
* di un json, in un oggetto destinazione.
*
* @param mapObject
* @param toFill
*/
@SuppressWarnings({ "unchecked"})
public static final <T> void fillFromMap(Map<String,Object> mapObject, T toFill) throws InstantiationException {
mapObject.forEach( (k, v) -> {
if (v == null) return;
try {
k = jsonNameToUpperCC(k);
// La chiave k non è in upperCamelCase, segue lo stile del json in cui tutto è minuscolo e i campi sono separati
// da trattini bassi.
Class<?> dest = PropertyUtils.getPropertyType(toFill, k);
Class<?> source = v.getClass();
if ( source == String.class ) {
final String vs = (String) v;
if ( dest == (new byte[0]).getClass() ) {
BeanUtils.setProperty(toFill, k, Base64Utilities.decode(vs.getBytes()));
}
else if ( dest == String.class ) {
BeanUtils.setProperty(toFill, k, vs);
}
else if ( dest.isEnum() ) {
boolean found = false;
Object[] constants = dest.getEnumConstants();
if (constants == null)
throw new IllegalArgumentException("La classe passata non è un'enumerazione");
for ( Object e : constants) {
if (String.valueOf(e.toString()).equals(vs.trim())) {
found = true;
BeanUtils.setProperty(toFill, k, e);
break;
}
}
if (!found)
throw new IllegalArgumentException("Impossibile deserialzzare l'oggetto di valore: " + vs + " e classe destinazione " + dest.toGenericString());
}
else if ( dest == Integer.class ) {
BeanUtils.setProperty(toFill, k, Integer.valueOf(vs));
}
else if (dest == Object.class ) {
BeanUtils.setProperty(toFill, k, vs);
}
// TODO: Qui potrei sollevare un'eccezione se il campo non c'è nell'oggetto..
}
else if ( source == LinkedHashMap.class && dest != Object.class) {
// Se dobbiamo deserializzare una Map, abbiamo bisogno che la destinazione sia tipata e non un semplice object.
BeanUtils.setProperty(toFill, k, fromMap((Map<String,Object>)v, dest));
}
else { // Fallback, li assumo dello stesso tipo, se non lo sono, ci pensa setProperty a sollevare eccezione.
BeanUtils.setProperty(toFill, k, v);
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
}
});
}
public static final Map<ProfiloEnum,String> tipoProtocolloFromProfilo = ProfiloUtils.getMapProfiloToProtocollo();
public static final Map<String,ProfiloEnum> profiloFromTipoProtocollo = ProfiloUtils.getMapProtocolloToProfilo();
public static <T> void validateBean(T bean) {
if (bean == null) return;
JAXRSParameterNameProvider parameterNameProvider = new JAXRSParameterNameProvider();
Configuration<?> factoryCfg = Validation.byDefaultProvider().configure();
ValidationConfiguration cfg = new ValidationConfiguration(parameterNameProvider);
if (cfg != null) {
factoryCfg.parameterNameProvider(cfg.getParameterNameProvider());
factoryCfg.messageInterpolator(cfg.getMessageInterpolator());
factoryCfg.traversableResolver(cfg.getTraversableResolver());
factoryCfg.constraintValidatorFactory(cfg.getConstraintValidatorFactory());
for (Map.Entry<String, String> entry : cfg.getProperties().entrySet()) {
factoryCfg.addProperty(entry.getKey(), entry.getValue());
}
}
ValidatorFactory factory = factoryCfg.buildValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<T>> violations = validator.validate(bean);
if (!violations.isEmpty()) {
ProblemValidation problem = new ProblemValidation(FaultCode.RICHIESTA_NON_VALIDA.toFault());
for (ConstraintViolation<T> violation : violations) {
String msg = bean.getClass().getSimpleName() + "." + violation.getPropertyPath();
problem.addInvalidParam(msg, violation.getMessage(), null);
}
throw FaultCode.RICHIESTA_NON_VALIDA.toException(Response.status(problem.getStatus()).entity(problem).type(HttpConstants.CONTENT_TYPE_JSON_PROBLEM_DETAILS_RFC_7807).build());
}
}
public static boolean validateAfterDeserialize = true;
@SuppressWarnings("unchecked")
public static <T> T deserialize(Object o, Class<T> dest) {
T ret = null;
if (o != null) {
if(dest.isInstance(o))
{
ret = dest.cast(o);
}
else
{
try {
ret = fromMap((Map<String, Object>) o,dest);
} catch (Exception e) {
throw FaultCode.RICHIESTA_NON_VALIDA.toException("Impossibile deserializzare l'oggetto " + dest.getName() + ", formato non valido: " + e.getMessage());
}
}
}
if(validateAfterDeserialize) {
validateBean(ret);
}
return ret;
}
public static <T> Optional<T> deserializeOptional(Object o, Class<T> dest) {
return Optional.ofNullable(deserialize(o,dest));
}
public static <T> T deserializeDefault(Object o, Class<T> dest) {
Optional<T> ret = deserializeOptional(o, dest);
if (
(!ret.isPresent())
||
(ret.isPresent() && ret.get() == null)
) {
try {
return Utilities.newInstance(dest);
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
return ret.get();
}
}
}