net.yadaframework.components.YadaWebUtil Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of yadaweb Show documentation
Show all versions of yadaweb Show documentation
Some useful tasks for the Yada Framework
package net.yadaframework.components;
import static net.yadaframework.core.YadaConstants.KEY_NOTIFICATION_AUTOCLOSE;
import static net.yadaframework.core.YadaConstants.KEY_NOTIFICATION_BODY;
import static net.yadaframework.core.YadaConstants.KEY_NOTIFICATION_CALLSCRIPT;
import static net.yadaframework.core.YadaConstants.KEY_NOTIFICATION_REDIRECT;
import static net.yadaframework.core.YadaConstants.KEY_NOTIFICATION_RELOADONCLOSE;
import static net.yadaframework.core.YadaConstants.KEY_NOTIFICATION_SEVERITY;
import static net.yadaframework.core.YadaConstants.KEY_NOTIFICATION_TITLE;
import static net.yadaframework.core.YadaConstants.KEY_NOTIFICATION_TOTALSEVERITY;
import static net.yadaframework.core.YadaConstants.VAL_NOTIFICATION_SEVERITY_ERROR;
import static net.yadaframework.core.YadaConstants.VAL_NOTIFICATION_SEVERITY_INFO;
import static net.yadaframework.core.YadaConstants.VAL_NOTIFICATION_SEVERITY_OK;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Entities.EscapeMode;
import org.jsoup.safety.Cleaner;
import org.jsoup.safety.Safelist;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.ui.Model;
import org.springframework.util.StreamUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.FrameworkServlet;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springframework.web.servlet.support.AbstractDispatcherServletInitializer;
import org.springframework.web.servlet.support.RequestContextUtils;
import org.springframework.web.util.UriComponentsBuilder;
import jakarta.servlet.ServletContext;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import net.yadaframework.core.YadaConfiguration;
import net.yadaframework.core.YadaConstants;
import net.yadaframework.core.YadaLocalEnum;
import net.yadaframework.exceptions.YadaInvalidUsageException;
import net.yadaframework.exceptions.YadaSystemException;
import net.yadaframework.web.YadaPageRequest;
import net.yadaframework.web.YadaPageRows;
@Lazy // Lazy because used in YadaCmsConfiguration, and it woud give a circular refecence exception otherwise
@Service
public class YadaWebUtil {
private final transient Logger log = LoggerFactory.getLogger(getClass());
@Autowired private YadaConfiguration config;
@Autowired private YadaUtil yadaUtil;
@Autowired private MessageSource messageSource;
public final YadaPageRequest FIND_ONE = YadaPageRequest.of(0, 1);
// Characters that should never be found or placed in a slug
private static final String PATTERN_INVALID_SLUG = "[?%:,;=&!+~()@*$'\"\\s]";
private Map> sortedLocalEnumCache = new HashMap<>();
/**
* Returns true if the MultipartFile has not been uploaded at all, not even an empty file
* @param multipartFile
*/
public boolean isMultipartMissing(MultipartFile multipartFile) {
// It's not enough to check for null or isEmpty() or size() because an empty file wouldn't be accepted.
// The only condition when no file has been uploaded at all is that the original filename is missing.
return multipartFile==null || StringUtils.isEmpty(multipartFile.getOriginalFilename());
}
/**
* Creates a dynamic @RequestMapping i.e. there's no need for it to be annotated and located in a controller instance.
* Useful if the url can be user-defined via some cms for example.
* @param newPath some url that the code will handle, like "/mypage"
* @param controllerInstance the instance of a class that will handle the page. It can be a @Controller but it's not required.
* @param methodName the name of the method that will handle the page. The method signature must be (Model, Locale).
* @param requestMappingHandlerMapping retrieve this via autowiring because it can't be autowired in YadaWebUtil directly
*/
public void registerDynamicMapping(String newPath, Object controllerInstance, String methodName, RequestMappingHandlerMapping requestMappingHandlerMapping) {
try {
Method method = controllerInstance.getClass().getMethod(methodName, Model.class, Locale.class);
RequestMappingInfo requestMappingInfo = RequestMappingInfo
.paths(newPath)
// There could be many conditions here
// .methods(RequestMethod.GET)
// .produces(MediaType.APPLICATION_JSON_VALUE)
.build();
requestMappingHandlerMapping.registerMapping(requestMappingInfo, controllerInstance, method);
} catch (Exception e) {
throw new YadaInvalidUsageException("Can't create dynamic mapping {} for {}.{}(model, locale)", newPath, controllerInstance.getClass(), methodName, e);
}
}
/**
* Returns the application-relative url of a file from the contents folder
* @param someContentFile some file that is inside the contents folder at any depth
* @return a string like "/contents/path/to/file.gif"
*/
public String getContentUrlRelative(File someContentFile) {
return "/" + config.getContentName() + "/" + yadaUtil.relativize(config.getContentsFolder(), someContentFile);
}
/**
* Set a cookie
* @param cookieName
* @param cookieValue
* @param expirationDays can be 0 to delete the cookie or <0 to persist until browser shutdown
* @param response
*/
public void setCookie(String cookieName, String cookieValue, int expirationDays, HttpServletResponse response) {
Cookie cookie = new Cookie(cookieName, cookieValue);
cookie.setMaxAge(60*60*expirationDays);
response.addCookie(cookie);
}
/**
* Returns the value of a cookie, null if not defined
* @param request
* @param cookieName
*/
public String getCookieValue(HttpServletRequest request, String cookieName) {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookieName.equals(cookie.getName())) {
return cookie.getValue();
}
}
}
return null;
}
/**
* Save to disk a base64 image received from javascript.
* @param base64Image the image string in the format data:image/png;base64,xxxxxxxxx where image/png can be any image format and xxxxxxxxx is the encoded image
* @param targetFileNoExtension the target file without extension: the extension is taken from base64Image
* @throws YadaInvalidUsageException if the image can't be decoded
* @throws YadaSystemException if saving fails
* @return the saved file (with extension)
*/
public File saveBase64Image(String base64Image, File targetFileNoExtension) {
String extension;
byte[] decodedImg;
try {
String[] parts = base64Image.split(",");
extension = parts[0].split("/")[1].split(";")[0];
String imageString = parts[1];
decodedImg = Base64.getDecoder().decode(imageString.getBytes());
} catch (Exception e) {
throw new YadaInvalidUsageException("Can't save base64Image that starts with \"{}\" to {}", base64Image.substring(0, 30), targetFileNoExtension, e);
}
File parentFolder = targetFileNoExtension.getParentFile();
if (!parentFolder.exists()) {
parentFolder.mkdirs();
}
File targetFile = new File(parentFolder, targetFileNoExtension.getName() + "." + extension);
try (FileOutputStream fos = new FileOutputStream(targetFile)) {
fos.write(decodedImg);
} catch (IOException e) {
throw new YadaSystemException("Can't save base64Image to {}", targetFileNoExtension, e);
}
return targetFile;
}
/**
* Perform autowiring of an instance that doesn't come from the Spring context, e.g. a JPA @Entity or normal java instance made with new.
* Post processing (@PostConstruct etc) and initialization are also performed.
* This version can inject beans from the WebApplicationContext like @Controllers. You may consider a class hierarchy redesign before using this method.
* @param instance to autowire
* @return the autowired/initialized bean instance, either the original or a wrapped one
* @see {@link YadaUtil#autowireAndInitialize(Object)}, {@link AutowireCapableBeanFactory#autowireBean(Object)}, {@link AutowireCapableBeanFactory#initializeBean(Object, String)}, {@link #autowire(Object)}
*/
public Object autowireAndInitialize(Object instance) {
HttpServletRequest request = getCurrentRequest();
if (request!=null) {
log.debug("Using YadaWebUtil.autowireAndInitialize(): please use YadaUtil.autowireAndInitialize() instead "
+ "and, if the bean is not found there, you should consider a redesign. No technical problem in going ahead here though.");
String attrName = FrameworkServlet.SERVLET_CONTEXT_PREFIX + AbstractDispatcherServletInitializer.DEFAULT_SERVLET_NAME;
ServletContext servletContext = request.getServletContext();
WebApplicationContext webApplicationContext = (WebApplicationContext) servletContext.getAttribute(attrName);
// WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
AutowireCapableBeanFactory autowireCapableBeanFactory = webApplicationContext.getAutowireCapableBeanFactory();
autowireCapableBeanFactory.autowireBean(instance);
return autowireCapableBeanFactory.initializeBean(instance, instance.getClass().getSimpleName());
}
log.error("No HttpServletRequest in current thread");
return null;
}
public boolean isEmpty(YadaPageRows> yadaPageRows) {
return yadaPageRows==null || yadaPageRows.isEmpty();
}
/**
* Returns the last part of the current request, for example from "/some/product" returns "product"
* @param request
* @return
* @see #getRequestMapping()
*/
public String getRequestMapping(HttpServletRequest request) {
String path = request.getServletPath();
String[] parts = path.split("/");
if (parts.length==0) {
return "/";
}
return parts[parts.length-1];
}
/**
* Returns the last part of the current request, for example from "/some/product" returns "product"
* This version does not require the request to be passed as parameter.
* @return
* @see #getRequestMapping(HttpServletRequest)
*/
public String getRequestMapping() {
ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
UriComponentsBuilder ucb = UriComponentsBuilder.fromPath(sra.getRequest().getRequestURI());
return ucb.build().getPathSegments().get(ucb.build().getPathSegments().size()-1);
}
/**
* If the url is a full url that points to our server, make it relative to the server and strip any language in the path.
* The result can be used in thymeleaf @{url} statements and will get the proper browser language when needed.
* @param fullUrlWithHttp something like "https://my.site.com/en/something/here", can be empty or null
* @param request
* @return someting like "/something/here", or null
*/
public String removeLanguageFromOurUrl(String fullUrlWithHttp, HttpServletRequest request) {
String url = StringUtils.trimToNull(fullUrlWithHttp); // "https://my.site.com/en/something/here"
if (url==null) {
return null;
}
String ourAddress = this.getWebappAddress(request); // "https://my.site.com"
if (url.startsWith(ourAddress)) {
url = url.substring(ourAddress.length()); // "/en/something/here"
if (config.isLocalePathVariableEnabled() && url.length()>=3) {
// If the url starts with a configured language, strip it
List localeStrings = config.getLocaleStrings();
for (String language : localeStrings) {
if (url.startsWith("/" + language + "/")) {
url = url.substring(language.length()+1); // "/something/here"
break;
}
}
}
}
return url;
}
/**
* Adds all request parameters to the Model, optionally filtering by name.
* Existing model attributes are not overwritten.
* @param model
* @param request
* @param nameFilter parameter names that should pass through to the Model
*/
public void passThrough(Model model, HttpServletRequest request, String ... nameFilter) {
Map parameterMap = request.getParameterMap();
Set nameFilterSet = new HashSet<>();
nameFilterSet.addAll(Arrays.asList(nameFilter));
for (Map.Entry param : parameterMap.entrySet()) {
String key = param.getKey();
if (nameFilterSet.isEmpty() || nameFilterSet.contains(key)) {
String[] valueArray = param.getValue();
Object value = valueArray.length==1?valueArray[0]:valueArray;
if (value!=null && !model.containsAttribute(key)) {
model.addAttribute(key, value);
}
}
}
}
/**
* Add a url parameter or change its value if present
* @param sourceUrl a full or relative url. Can also be just the query string starting with "?"
* @param paramName the name of the parameter, not urlencoded
* @param paramValue the value of the parameter, not urlencoded. Can be null to only have the paramName in the url
* @return
*/
// Not tested yet
public String addOrUpdateUrlParameter(String sourceUrl, String paramName, String paramValue) {
String encodedParamName = urlEncode(paramName);
String encodedParamValue = urlEncode(paramValue); // Can be null
String equalsAndValue = encodedParamValue==null? "" : "=" + encodedParamValue;
StringBuilder result = new StringBuilder();
int queryPos = sourceUrl.indexOf("?");
boolean found=false;
if (queryPos<0) {
// There is no query string yet
result.append(sourceUrl);
} else if (queryPos>0) {
// There is a query string already
result.append(sourceUrl.substring(0, queryPos));
}
result.append("?");
if (queryPos>-1) {
// Check existing parameters
String query = sourceUrl.substring(queryPos); // "?xxx=yyy&zzz"
String[] params = query.split("[?&]"); // ["xxx=yyy", "zzz"]
for (int i = 0; i < params.length; i++) {
String[] parts = params[i].split("=");
String name = parts[0];
if (name.equals(encodedParamName)) {
result.append(encodedParamName).append(equalsAndValue);
found = true;
} else {
result.append(params[i]);
}
if (i0) {
boolean isStart = true;
int questionPos = result.indexOf("?");
if (questionPos<0) {
result.append("?");
} else {
if (url.length()>questionPos+1) {
// There is some parameter already
isStart = false;
}
}
boolean isName = true;
String lastName = null;
for (String param : params) {
if (isName && !isStart) {
result.append("&");
}
if (isName) { // name
// null names are skipped together with their value
if (param!=null) {
result.append(param);
result.append("=");
}
lastName = param;
} else { // value
if (lastName!=null) {
// Null values remain empty
if (param!=null) {
result.append(param);
}
} else {
log.debug("Skipping null name and its value '{}'", param);
}
}
isStart=false;
isName=!isName;
}
}
return result.toString();
}
/**
* Make a zip file and send it to the client. The temp file is automatically deleted.
* @param returnedFilename the name of the file to create and send, with extension. E.g.: data.zip
* @param sourceFiles the files to zip
* @param filenamesNoExtension the name of each file in the zip - null to keep the original names
* @param ignoreErrors true to ignore errors when adding a file and keep going, false for an exception when a file can't be zipped
* @param response
*/
public void downloadZip(String returnedFilename, File[] sourceFiles, String[] filenamesNoExtension, boolean ignoreErrors, HttpServletResponse response) throws Exception {
File zipFile = null;
try {
zipFile = File.createTempFile("downloadZip", null);
yadaUtil.createZipFile(zipFile, sourceFiles, filenamesNoExtension, ignoreErrors);
response.setContentType("application/zip");
response.setHeader("Content-disposition", "attachment; filename=" + returnedFilename);
try (OutputStream out = response.getOutputStream(); FileInputStream in = new FileInputStream(zipFile)) {
IOUtils.copy(in,out);
}
} finally {
if (zipFile!=null) {
zipFile.delete();
}
}
}
/**
* Sorts a localized enum according to the locale specified
* @param localEnum the class of the enum, e.g. net.yadaframework.persistence.entity.YadaJobState.class
* @param locale
* @return a list of sorted enums of the given class
*/
public > List sortLocalEnum(Class localEnum, Locale locale) {
String key = localEnum.getName() + "." + locale.toString();
@SuppressWarnings("unchecked")
List result = (List) sortedLocalEnumCache.get(key);
if (result==null) {
T[] enums = localEnum.getEnumConstants();
Arrays.sort(enums, new Comparator() {
@Override
public int compare(T o1, T o2) {
String v1 = o1.toString(messageSource, locale);
String v2 = o2.toString(messageSource, locale);
return v1.compareTo(v2);
}
});
result = Arrays.asList(enums);
sortedLocalEnumCache.put(key, result);
}
return result;
}
/**
* Returns the first language in the request language header as a string.
* @return the language string, like "en_US", or "" if not found
*/
public String getBrowserLanguage() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String languageHeader = StringUtils.trimToEmpty(request.getHeader("Accept-Language")); // en-US,en-GB;q=0.9,en;q=0.8,it;q=0.7,es;q=0.6,la;q=0.5
int pos = languageHeader.indexOf(',');
if (pos>4) {
try {
return languageHeader.substring(0, pos);
} catch (Exception e) {
// Invalid header - ignored
}
}
return "";
}
/**
* Returns the first country in the request language header as a string.
* @return the country string, like "US", or "" if not found
*/
public String getBrowserCountry() {
String browserLanguage = getBrowserLanguage();
int pos = browserLanguage.indexOf('-');
if (pos>1) {
try {
return browserLanguage.substring(pos+1);
} catch (Exception e) {
// Invalid header - ignored
}
}
return "";
}
/**
* Save an uploaded file to a temporary file
* @param attachment
* @return the temporary file holding the uploaded file, or null if no file has bee attached
* @throws IOException, IllegalStateException
*/
public File saveAttachment(MultipartFile attachment) throws IOException {
if (!isMultipartMissing(attachment)) {
File targetFile = File.createTempFile("upload-", null);
saveAttachment(attachment, targetFile);
return targetFile;
}
return null;
}
/**
* Save an uploaded file to the given target file
* @param attachment
* @param targetPath
* @throws IOException, IllegalStateException
*/
public void saveAttachment(MultipartFile attachment, Path targetPath) throws IOException {
saveAttachment(attachment, targetPath.toFile());
// try (InputStream inputStream = attachment.getInputStream(); OutputStream outputStream = new FileOutputStream(targetPath.toFile())) {
// IOUtils.copy(inputStream, outputStream);
// }
}
/**
* Save an uploaded file to the given target file
* @param attachment
* @param targetFile
* @throws IOException, IllegalStateException
*/
public void saveAttachment(MultipartFile attachment, File targetFile) throws IOException {
attachment.transferTo(targetFile);
//
// try (InputStream inputStream = attachment.getInputStream(); OutputStream outputStream = new FileOutputStream(targetFile)) {
// IOUtils.copy(inputStream, outputStream);
// }
}
/**
* Fix a url so that it valid and doesn't allow XSS attacks
* @see https://cheatsheetseries.owasp.org/cheatsheets/XSS_Filter_Evasion_Cheat_Sheet.html
* @param url some text typed by the user
* @return the same url or null if blank or a fixed one that may or may not work as expected, but won't pose a security risk
*/
public String sanitizeUrl(String url) {
if (StringUtils.isBlank(url)) {
return null;
}
url = url.replaceAll("[^-A-Za-z0-9+&@#/%?=~_|!:,.;\\(\\)]", "");
if (!url.startsWith("http://") && !url.startsWith("https://")) {
url = "http://" + url;
}
return url;
}
/**
* Assembles a url given its parts as string.
* @param segments the initial parts of the url up to the query string. Leading and trailing slashes are added when missing.
* URLEncoding is not performed.
* @return
* @see #makeUrl(String[], Map, Boolean)
*/
public String makeUrl(String...segments) {
return makeUrl(segments, null, null);
}
/**
* Assembles a url given its parts as string.
* @param segments the initial parts of the url up to the query string. Leading and trailing slashes are added when missing.
* @param requestParams optional name/value pairs of request parameters that will compose the query string.
* Use null for no parameters, use a null value for no value (just the name will be added)
* @param urlEncode use Boolean.TRUE to encode the parameters, null or anything else not to encode
* @return
*/
public String makeUrl(String[] segments, Map requestParams, Boolean urlEncode) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < segments.length; i++) {
if (segments[i]==null) {
continue;
}
result.append(segments[i]);
// Add a separator when needed
if (i modelMap = redirectAttributes.getFlashAttributes();
// Mette nel flash tre array di stringhe che contengono titolo, messaggio e severity.
if (!modelMap.containsKey(KEY_NOTIFICATION_TITLE)) {
List titles = new ArrayList<>();
List bodies = new ArrayList<>();
List severities = new ArrayList<>();
redirectAttributes.addFlashAttribute(KEY_NOTIFICATION_TITLE, titles);
redirectAttributes.addFlashAttribute(KEY_NOTIFICATION_BODY, bodies);
redirectAttributes.addFlashAttribute(KEY_NOTIFICATION_SEVERITY, severities);
}
// Aggiunge i nuovi valori
((List)modelMap.get(KEY_NOTIFICATION_TITLE)).add(title);
((List)modelMap.get(KEY_NOTIFICATION_BODY)).add(message);
((List)modelMap.get(KEY_NOTIFICATION_SEVERITY)).add(severity);
String newTotalSeverity = calcTotalSeverity(modelMap, severity);
redirectAttributes.addFlashAttribute(KEY_NOTIFICATION_TOTALSEVERITY, newTotalSeverity);
}
/**
* Test if a modal is going to be opened when back to the view (usually after a redirect)
* @param request
* @return true if a modal is going to be opened
* @deprecated Use YadaNotify instead
*/
@Deprecated
public boolean isNotifyModalPending(HttpServletRequest request) {
Map flashMap = RequestContextUtils.getInputFlashMap(request);
return flashMap!=null && (
flashMap.containsKey(KEY_NOTIFICATION_TITLE)
|| flashMap.containsKey(KEY_NOTIFICATION_BODY)
|| flashMap.containsKey(KEY_NOTIFICATION_SEVERITY)
);
}
/**
* Da usare direttamente solo quando si vuole fare un redirect dopo aver mostrato un messaggio.
* Se chiamato tante volte, i messaggi si sommano e vengono mostrati tutti all'utente.
* @param title
* @param message
* @param severity a string like YadaConstants.VAL_NOTIFICATION_SEVERITY_OK
* @param redirectSemiurl e.g. "/user/profile"
* @param model
* @see YadaConstants
* @deprecated Use YadaNotify instead
*/
@Deprecated
public void notifyModal(String title, String message, String severity, String redirectSemiurl, Model model) {
if (severity==VAL_NOTIFICATION_SEVERITY_ERROR) {
// Tutte le notifiche di errore vengono loggate a warn (potrebbero non essere degli errori del programma)
log.warn("notifyModal: {} - {}", title, message);
}
// Mette nel model tre array di stringhe che contengono titolo, messaggio e severity.
if (!model.containsAttribute(KEY_NOTIFICATION_TITLE)) {
List titles = new ArrayList<>();
List bodies = new ArrayList<>();
List severities = new ArrayList<>();
model.addAttribute(KEY_NOTIFICATION_TITLE, titles);
model.addAttribute(KEY_NOTIFICATION_BODY, bodies);
model.addAttribute(KEY_NOTIFICATION_SEVERITY, severities);
}
// Aggiunge i nuovi valori
Map modelMap = model.asMap();
((List)modelMap.get(KEY_NOTIFICATION_TITLE)).add(title);
((List)modelMap.get(KEY_NOTIFICATION_BODY)).add(message);
((List)modelMap.get(KEY_NOTIFICATION_SEVERITY)).add(severity);
// Il redirect è sempre uno solo: prevale l'ultimo
if (redirectSemiurl!=null) {
model.addAttribute(KEY_NOTIFICATION_REDIRECT, redirectSemiurl);
}
String newTotalSeverity = calcTotalSeverity(modelMap, severity);
model.addAttribute(KEY_NOTIFICATION_TOTALSEVERITY, newTotalSeverity);
}
/**
* Return true if modalError has been called before
* @param model
* @return
* @deprecated Use YadaNotify instead
*/
@Deprecated
public boolean isModalError(Model model) {
return model.asMap().containsValue(VAL_NOTIFICATION_SEVERITY_ERROR);
}
/**
* Return true if for this thread the notifyModal (or a variant) has been called
* @param model
* @return
* @deprecated Use YadaNotify instead
*/
@Deprecated
public boolean isNotifyModalRequested(Model model) {
return model.containsAttribute(KEY_NOTIFICATION_TITLE);
}
/**
* Calcola la severity totale del dialog dalla severity di tutti i messaggi esistenti (è la più alta tra tutti)
* @param modelMap
* @param lastSeverity
* @return
*/
@Deprecated
private String calcTotalSeverity(Map modelMap, String lastSeverity) {
// Algoritmo:
// - se la total è error, resta error;
// - se lastSeverity è ok e quella total è info, resta info;
// - per cui
// - se lastSeverity è error, diventa o resta error;
// - se lastSeverity è info, diventa o resta info;
// - se lastSeverity è ok, diventa ok visto che total non è nè error nè info;
String newTotalSeverity = lastSeverity;
String currentTotalSeverity = (String) modelMap.get(KEY_NOTIFICATION_TOTALSEVERITY);
if (VAL_NOTIFICATION_SEVERITY_ERROR.equals(currentTotalSeverity)) {
return currentTotalSeverity; // ERROR wins always
}
if (VAL_NOTIFICATION_SEVERITY_OK.equals(lastSeverity) && VAL_NOTIFICATION_SEVERITY_INFO.equals(currentTotalSeverity)) {
newTotalSeverity = currentTotalSeverity; // INFO wins over OK
}
return newTotalSeverity;
}
// private void notifyModalSetValue(String title, String message, String severity, String redirectSemiurl, Model model) {
// model.addAttribute(KEY_NOTIFICATION_TITLE, title);
// model.addAttribute(KEY_NOTIFICATION_BODY, message);
// model.addAttribute(KEY_NOTIFICATION_SEVERITY, severity);
// if (redirectSemiurl!=null) {
// model.addAttribute(KEY_NOTIFICATION_REDIRECT, redirectSemiurl);
// }
// }
/**
* Returns the browser's remote ip address.
* If the connection uses a proxy that sets the "X-Forwarded-For" header, the result is taken from that header.
* @param request
* @return The client IP address, ignoring any proxy address when possible
*/
public String getClientAddress(HttpServletRequest request) {
String forwardedFor = request.getHeader("X-Forwarded-For"); // X-Forwarded-For: 203.0.113.195, 70.41.3.18, 150.172.238.178
if (!StringUtils.isBlank(forwardedFor)) {
return forwardedFor.split(",", 2)[0].trim();
}
return request.getRemoteAddr();
}
@Deprecated // Quite useless because of the format of the result
public String getClientIp(HttpServletRequest request) {
String remoteAddr = request.getRemoteAddr();
String forwardedFor = request.getHeader("X-Forwarded-For");
String remoteIp = "?";
if (!StringUtils.isBlank(remoteAddr)) {
remoteIp = remoteAddr;
}
if (!StringUtils.isBlank(forwardedFor)) {
remoteIp = "[for " + forwardedFor + "]";
}
return remoteIp;
}
/**
*
* @param message text to be displayed (can be null for default value)
* @param confirmButton text of the confirm button (can be null for default value)
* @param cancelButton text of the cancel button (can be null for default value)
* @param model
*/
public String modalConfirm(String message, String confirmButton, String cancelButton, Model model) {
return modalConfirm(message, confirmButton, cancelButton, model, false, false);
}
/**
* Show the confirm modal and reloads when the confirm button is pressed, adding the confirmation parameter to the url.
* The modal will be opened on load.
* Usually used by non-ajax methods.
* @param message text to be displayed (can be null for default value)
* @param confirmButton text of the confirm button (can be null for default value)
* @param cancelButton text of the cancel button (can be null for default value)
* @param model
*/
public String modalConfirmAndReload(String message, String confirmButton, String cancelButton, Model model) {
return modalConfirm(message, confirmButton, cancelButton, model, true, true);
}
/**
* Show the confirm modal and optionally reloads when the confirm button is pressed
* @param message text to be displayed (can be null for default value)
* @param confirmButton text of the confirm button (can be null for default value)
* @param cancelButton text of the cancel button (can be null for default value)
* @param model
* @param reloadOnConfirm (optional) when true, the browser will reload on confirm, adding the confirmation parameter to the url
* @param openModal (optional) when true, the modal will be opened. To be used when the call is not ajax
*/
public String modalConfirm(String message, String confirmButton, String cancelButton, Model model, Boolean reloadOnConfirm, Boolean openModal) {
model.addAttribute("message", message);
model.addAttribute("confirmButton", confirmButton);
model.addAttribute("cancelButton", cancelButton);
if (openModal) {
model.addAttribute("openModal", true);
}
if (reloadOnConfirm) {
model.addAttribute("reloadOnConfirm", true);
}
return "/yada/modalConfirmB" + config.getBootstrapVersion();
}
/**
* Add a script id to call when opening the notification modal
* @param scriptId
* @param model
* @deprecated Use YadaNotify instead
*/
@Deprecated
public void callScriptOnModal(String scriptId, Model model) {
if (!model.containsAttribute(KEY_NOTIFICATION_CALLSCRIPT)) {
List scriptIds = new ArrayList<>();
model.addAttribute(KEY_NOTIFICATION_CALLSCRIPT, scriptIds);
}
Map modelMap = model.asMap();
((List)modelMap.get(KEY_NOTIFICATION_CALLSCRIPT)).add(scriptId);
}
}