org.fao.geonet.util.XslUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.fao.geonet.util.XslUtil.java

Source

/*
 * Copyright (C) 2001-2017 Food and Agriculture Organization of the
 * United Nations (FAO-UN), United Nations World Food Programme (WFP)
 * and United Nations Environment Programme (UNEP)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 *
 * Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2,
 * Rome - Italy. email: geonetwork@osgeo.org
 */

package org.fao.geonet.util;

import static org.fao.geonet.kernel.search.spatial.SpatialIndexWriter.parseGml;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.Locale;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.impl.client.HttpClientBuilder;
import org.fao.geonet.ApplicationContextHolder;
import org.fao.geonet.SystemInfo;
import org.fao.geonet.constants.Geonet;
import org.fao.geonet.domain.UiSetting;
import org.fao.geonet.domain.User;
import org.fao.geonet.kernel.DataManager;
import org.fao.geonet.kernel.SchemaManager;
import org.fao.geonet.kernel.ThesaurusManager;
import org.fao.geonet.kernel.search.CodeListTranslator;
import org.fao.geonet.kernel.search.LuceneSearcher;
import org.fao.geonet.kernel.search.Translator;
import org.fao.geonet.kernel.setting.SettingInfo;
import org.fao.geonet.kernel.setting.SettingManager;
import org.fao.geonet.languages.IsoLanguagesMapper;
import org.fao.geonet.lib.Lib;
import org.fao.geonet.repository.UiSettingsRepository;
import org.fao.geonet.repository.UserRepository;
import org.fao.geonet.schema.iso19139.ISO19139Namespaces;
import org.fao.geonet.utils.GeonetHttpRequestFactory;
import org.fao.geonet.utils.Log;
import org.fao.geonet.utils.Xml;
import org.geotools.geometry.jts.JTS;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.gml3.GMLConfiguration;
import org.geotools.referencing.CRS;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.xml.Parser;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.output.DOMOutputter;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.owasp.esapi.errors.EncodingException;
import org.owasp.esapi.reference.DefaultEncoder;
import org.springframework.context.ApplicationContext;
import org.springframework.http.client.ClientHttpResponse;
import org.w3c.dom.Node;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Function;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.MultiPolygon;

import jeeves.component.ProfileManager;
import jeeves.server.ServiceConfig;
import jeeves.server.context.ServiceContext;
import net.objecthunter.exp4j.Expression;
import net.objecthunter.exp4j.ExpressionBuilder;

/**
 * These are all extension methods for calling from xsl docs.  Note:  All params are objects because
 * it is hard to determine what is passed in from XSLT. Most are converted to string by calling
 * tostring.
 *
 * @author jesse
 */
public final class XslUtil {

    private static final char TS_DEFAULT = ' ';
    private static final char CS_DEFAULT = ',';
    private static final char TS_WKT = ',';
    private static final char CS_WKT = ' ';
    private static ThreadLocal<Boolean> allowScripting = new InheritableThreadLocal<Boolean>();

    /**
     * clean the src of ' and <>
     */
    public static String clean(Object src) {
        String result = src.toString().replaceAll("'", "\'").replaceAll("[><\n\r]", " ");
        return result;
    }

    /**
     * Returns 'true' if the pattern matches the src
     */
    public static String countryMatch(Object src, Object pattern) {
        if (src.toString().trim().length() == 0) {
            return "false";
        }
        boolean result = src.toString().toLowerCase().contains(pattern.toString().toLowerCase());
        return String.valueOf(result);
    }

    /**
     * Replace the pattern with the substitution
     */
    public static String replace(Object src, Object pattern, Object substitution) {
        String result = src.toString().replaceAll(pattern.toString(), substitution.toString());
        return result;
    }

    public static boolean isCasEnabled() {
        return ProfileManager.isCasEnabled();
    }

    /**
     * Return a service handler config parameter
     *
     * @see org.fao.geonet.constants.Geonet.Config
     */
    public static String getConfigValue(String key) {
        if (key == null) {
            return "";
        }

        ServiceConfig config = ApplicationContextHolder.get().getBean(ServiceConfig.class);
        if (config != null) {
            String value = config.getValue(key);
            if (value != null) {
                return value;
            } else {
                return "";
            }
        }
        return "";
    }

    public static String getBuildNumber() {
        return ApplicationContextHolder.get().getBean(SystemInfo.class).getScmRevision();
    }

    /**
     * Get the UI configuration. Return the JSON string.
     * @param key Optional key, if null, return a default configuration nammed 'srv'
     *            if exist. If not, empty config is returned.
     * @return
     */
    public static String getUiConfiguration(String key) {
        final String defaultUiConfiguration = "srv";
        UiSettingsRepository uiSettingsRepository = ApplicationContextHolder.get()
                .getBean(UiSettingsRepository.class);

        if (uiSettingsRepository != null) {
            UiSetting one = null;
            if (StringUtils.isNotEmpty(key)) {
                one = uiSettingsRepository.findOne(key);
            }
            if (one == null) {
                one = uiSettingsRepository.findOne(defaultUiConfiguration);
            }
            if (one != null) {
                return one.getConfiguration();
            } else {
                return "{}";
            }
        }
        return "{}";
    }

    /**
     * Get a precise value in the JSON UI configuration
     * @param key
     * @param path JSON path to the property
     * @return
     */
    public static String getUiConfigurationJsonProperty(String key, String path) {
        String json = getUiConfiguration(key);
        ObjectMapper objectMapper = new ObjectMapper();

        try {
            Object jsonObj = objectMapper.readValue(json, Object.class);

            Object value = PropertyUtils.getProperty(jsonObj, path);
            if (value != null) {
                return value.toString();
            } else {
                return "";
            }
        } catch (Exception e) {
            return "";
        }
    }

    /**
     * Get a setting value
     */
    public static String getSettingValue(String key) {
        if (key == null) {
            return "";
        }

        SettingManager settingsMan = ApplicationContextHolder.get().getBean(SettingManager.class);
        if (settingsMan != null) {
            String value;
            if ("nodeUrl".equals(key)) {
                value = settingsMan.getNodeURL();
            } else {
                value = settingsMan.getValue(key);
            }
            if (value != null) {
                return value;
            } else {
                return "";
            }
        }

        return "";
    }

    public static String getJsonSettingValue(String key, String path) {
        if (key == null) {
            return "";
        }
        try {
            final ServiceContext serviceContext = ServiceContext.get();
            if (serviceContext != null) {
                SettingManager settingsMan = serviceContext.getBean(SettingManager.class);
                if (settingsMan != null) {
                    String json = settingsMan.getValue(key);

                    if (StringUtils.isEmpty(json))
                        return "";

                    ObjectMapper objectMapper = new ObjectMapper();
                    Object jsonObj = objectMapper.readValue(json, Object.class);

                    Object value = PropertyUtils.getProperty(jsonObj, path);
                    if (value != null) {
                        return value.toString();
                    } else {
                        return "";
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }

    /**
     * Check if bean is defined in the context
     *
     * @param beanId id of the bean to look up
     */
    public static boolean existsBean(String beanId) {
        return ProfileManager.existsBean(beanId);
    }

    /**
     * Optimistically check if user can access a given url.  If not possible to determine then the
     * methods will return true.  So only use to show url links, not check if a user has access for
     * certain.  Spring security should ensure that users cannot access restricted urls though.
     *
     * @param serviceName the raw services name (main.home) or (admin)
     * @return true if accessible or system is unable to determine because the current thread does
     * not have a ServiceContext in its thread local store
     */
    public static boolean isAccessibleService(Object serviceName) {
        return ProfileManager.isAccessibleService(serviceName);
    }

    /**
     * Takes the characters until the pattern is matched
     */
    public static String takeUntil(Object src, Object pattern) {
        String src2 = src.toString();
        Matcher matcher = Pattern.compile(pattern.toString()).matcher(src2);

        if (!matcher.find())
            return src2;

        int index = matcher.start();

        if (index == -1) {
            return src2;
        }
        return src2.substring(0, index);
    }

    /**
     * Convert a serialized XML node in JSON
     */
    public static String xmlToJson(Object xml) {
        try {
            return Xml.getJSON(xml.toString());
        } catch (IOException e) {
            Log.error(Geonet.GEONETWORK,
                    "XMLtoJSON conversion I/O error. Error is " + e.getMessage() + ". XML is " + xml.toString());
        }
        return "";
    }

    /**
     * Converts the seperators of the coords to the WKT from ts and cs
     *
     * @param coords the coords string to convert
     * @param ts     the separator that separates 2 coordinates
     * @param cs     the separator between 2 numbers in a coordinate
     */
    public static String toWktCoords(Object coords, Object ts, Object cs) {
        String coordsString = coords.toString();
        char tsString;
        if (ts == null || ts.toString().length() == 0) {
            tsString = TS_DEFAULT;
        } else {
            tsString = ts.toString().charAt(0);
        }
        char csString;
        if (cs == null || cs.toString().length() == 0) {
            csString = CS_DEFAULT;
        } else {
            csString = cs.toString().charAt(0);
        }

        if (tsString == TS_WKT && csString == CS_WKT) {
            return coordsString;
        }

        if (tsString == CS_WKT) {
            tsString = ';';
            coordsString = coordsString.replace(CS_WKT, tsString);
        }
        coordsString = coordsString.replace(csString, CS_WKT);
        String result = coordsString.replace(tsString, TS_WKT);
        char lastChar = result.charAt(result.length() - 1);
        if (result.charAt(result.length() - 1) == TS_WKT || lastChar == CS_WKT) {
            result = result.substring(0, result.length() - 1);
        }
        return result;
    }

    public static String posListToWktCoords(Object coords, Object dim) {
        String[] coordsString = coords.toString().split(" ");

        int dimension;
        if (dim == null) {
            dimension = 2;
        } else {
            try {
                dimension = Integer.parseInt(dim.toString());
            } catch (NumberFormatException e) {
                dimension = 2;
            }
        }
        StringBuilder results = new StringBuilder();

        for (int i = 0; i < coordsString.length; i++) {
            if (i > 0 && i % dimension == 0) {
                results.append(',');
            } else if (i > 0) {
                results.append(' ');
            }
            results.append(coordsString[i]);
        }

        return results.toString();
    }

    /**
     * Get field value for metadata identified by uuid.
     *
     * @param appName Web application name to access Lucene index from environment variable
     * @param uuid    Metadata uuid
     * @param field   Lucene field name
     * @param lang    Language of the index to search in
     * @return metadata title or an empty string if Lucene index or uuid could not be found
     */
    public static String getIndexField(Object appName, Object uuid, Object field, Object lang) {
        String id = uuid.toString();
        String fieldname = field.toString();
        String language = (lang.toString().equals("") ? null : lang.toString());
        try {
            String fieldValue = LuceneSearcher.getMetadataFromIndex(language, id, fieldname);
            if (fieldValue == null) {
                return getIndexFieldById(appName, uuid, field, lang);
            } else {
                return fieldValue;
            }
        } catch (Exception e) {
            Log.error(Geonet.GEONETWORK, "Failed to get index field value caused by " + e.getMessage());
            return "";
        }
    }

    public static String getIndexFieldById(Object appName, Object id, Object field, Object lang) {
        String fieldname = field.toString();
        String language = (lang.toString().equals("") ? null : lang.toString());
        try {
            String fieldValue = LuceneSearcher.getMetadataFromIndexById(language, id.toString(), fieldname);
            return fieldValue == null ? "" : fieldValue;
        } catch (Exception e) {
            Log.error(Geonet.GEONETWORK, "Failed to get index field value caused by " + e.getMessage());
            return "";
        }
    }

    /**
     * Return a translation for a codelist or enumeration element.
     *
     * @param codelist The codelist name (eg. gmd:MD_TopicCategoryCode)
     * @param value    The value to search for in the translation file
     * @param langCode The language
     * @return The translation, the code list value if not found or an empty string if no codelist
     * value provided.
     */
    public static String getCodelistTranslation(Object codelist, Object value, Object langCode) {
        String codeListValue = (String) value;
        if (codeListValue != null && codelist != null && langCode != null) {
            String translation = codeListValue;
            try {
                Translator t = new CodeListTranslator(ApplicationContextHolder.get().getBean(SchemaManager.class),
                        (String) langCode, (String) codelist);
                translation = t.translate(codeListValue);
            } catch (Exception e) {
                Log.error(Geonet.GEONETWORK,
                        String.format("Failed to translate codelist value '%s' in language '%s'. Error is %s",
                                codeListValue, langCode, e.getMessage()));
            }
            return translation;
        } else {
            return "";
        }
    }

    /**
     * Return 2 iso lang code from a 3 iso lang code. If any error occurs return "".
     *
     * @param iso3LangCode The 2 iso lang code
     * @return The related 3 iso lang code
     */
    public static @Nonnull String twoCharLangCode(String iso3LangCode) {
        return twoCharLangCode(iso3LangCode, twoCharLangCode(Geonet.DEFAULT_LANGUAGE, null));
    }

    /**
     * Return 2 iso lang code from a 3 iso lang code. If any error occurs return "".
     *
     * @param iso3LangCode The 2 iso lang code
     * @return The related 3 iso lang code
     */
    public static @Nonnull String twoCharLangCode(String iso3LangCode, String defaultValue) {
        if (iso3LangCode == null || iso3LangCode.length() == 0) {
            if (defaultValue != null) {
                return defaultValue;
            } else {
                return twoCharLangCode(Geonet.DEFAULT_LANGUAGE);
            }
        } else {
            if (iso3LangCode.equalsIgnoreCase("FRA")) {
                return "FR";
            }

            if (iso3LangCode.equalsIgnoreCase("DEU")) {
                return "DE";
            }
            String iso2LangCode = null;

            try {
                if (iso3LangCode.length() == 2) {
                    iso2LangCode = iso3LangCode;
                } else {
                    final IsoLanguagesMapper mapper = ApplicationContextHolder.get()
                            .getBean(IsoLanguagesMapper.class);
                    iso2LangCode = mapper.iso639_2_to_iso639_1(iso3LangCode);
                }
            } catch (Exception ex) {
                Log.error(Geonet.GEONETWORK,
                        "Failed to get iso 2 language code for " + iso3LangCode + " caused by " + ex.getMessage());

            }

            if (iso2LangCode == null) {
                Log.error(Geonet.GEONETWORK, "Cannot convert " + iso3LangCode + " to 2 char iso lang code",
                        new Error());
                return iso3LangCode.substring(0, 2);
            } else {
                return iso2LangCode;
            }
        }
    }

    /**
     * Returns the HTTP code  or error message if error occurs during URL connection.
     *
     * @param url The URL to ckeck.
     * @return the numeric code of the HTTP request or a String with an error.
     */
    public static String getUrlStatus(String url) {
        return getUrlStatus(url, 5);

    }

    /**
     * Returns the HTTP code  or error message if error occurs during URL connection.
     *
     * @param url       The URL to ckeck.
     * @param tryNumber the number of remaining tries.
     */
    public static String getUrlStatus(String url, int tryNumber) {
        if (tryNumber < 1) {
            // protect against redirect loops
            return "ERR_TOO_MANY_REDIRECTS";
        }
        HttpHead head = new HttpHead(url);
        GeonetHttpRequestFactory requestFactory = ApplicationContextHolder.get()
                .getBean(GeonetHttpRequestFactory.class);
        ClientHttpResponse response = null;
        try {
            response = requestFactory.execute(head, new Function<HttpClientBuilder, Void>() {
                @Nullable
                @Override
                public Void apply(@Nullable HttpClientBuilder originalConfig) {
                    RequestConfig.Builder config = RequestConfig.custom().setConnectTimeout(1000)
                            .setConnectionRequestTimeout(3000).setSocketTimeout(5000);
                    RequestConfig requestConfig = config.build();
                    originalConfig.setDefaultRequestConfig(requestConfig);

                    return null;
                }
            });
            //response = requestFactory.execute(head);
            if (response.getRawStatusCode() == HttpStatus.SC_BAD_REQUEST
                    || response.getRawStatusCode() == HttpStatus.SC_METHOD_NOT_ALLOWED
                    || response.getRawStatusCode() == HttpStatus.SC_INTERNAL_SERVER_ERROR) {
                // the website doesn't support HEAD requests. Need to do a GET...
                response.close();
                HttpGet get = new HttpGet(url);
                response = requestFactory.execute(get);
            }

            if (response.getStatusCode().is3xxRedirection() && response.getHeaders().containsKey("Location")) {
                // follow the redirects
                return getUrlStatus(response.getHeaders().getFirst("Location"), tryNumber - 1);
            }

            return String.valueOf(response.getRawStatusCode());
        } catch (IOException e) {
            Log.error(Geonet.GEONETWORK, "IOException validating  " + url + " URL. " + e.getMessage(), e);
            return e.getMessage();
        } finally {
            if (response != null) {
                response.close();
            }
        }
    }

    public static String threeCharLangCode(String langCode) {
        if (langCode == null || langCode.length() < 2) {
            return Geonet.DEFAULT_LANGUAGE;
        }

        if (langCode.length() == 3) {
            return langCode;
        }

        final IsoLanguagesMapper mapper;
        mapper = ApplicationContextHolder.get().getBean(IsoLanguagesMapper.class);
        return mapper.iso639_1_to_iso639_2(langCode);

    }

    public static boolean match(Object src, Object pattern) {
        if (src == null || src.toString().trim().isEmpty()) {
            return false;
        }
        return src.toString().matches(pattern.toString());
    }

    public static void setNoScript() {
        allowScripting.set(false);
    }

    public static boolean allowScripting() {
        return allowScripting.get() == null || allowScripting.get();
    }

    public static String getUserDetails(Object contactIdentifier) {
        String contactDetails = "";
        int contactId = Integer.parseInt((String) contactIdentifier);

        User user = ApplicationContextHolder.get().getBean(UserRepository.class).findOne(contactId);
        if (user != null) {
            contactDetails = Xml.getString(user.asXml());
        }

        return contactDetails;
    }

    public static String reprojectCoords(Object minx, Object miny, Object maxx, Object maxy, Object fromEpsg) {
        String ret = "";
        try {
            Double minxf = new Double((String) minx);
            Double minyf = new Double((String) miny);
            Double maxxf = new Double((String) maxx);
            Double maxyf = new Double((String) maxy);
            CoordinateReferenceSystem fromCrs = CRS.decode((String) fromEpsg);
            CoordinateReferenceSystem toCrs = CRS.decode("EPSG:4326");

            ReferencedEnvelope env = new ReferencedEnvelope(minxf, maxxf, minyf, maxyf, fromCrs);
            ReferencedEnvelope reprojected = env.transform(toCrs, true);

            ret = reprojected.getMinX() + "," + reprojected.getMinY() + "," + reprojected.getMaxX() + ","
                    + reprojected.getMaxY();

            Element elemRet = new Element("EX_GeographicBoundingBox", ISO19139Namespaces.GMD);

            boolean forceXY = Boolean.getBoolean(System.getProperty("org.geotools.referencing.forceXY", "false"));
            Element elemminx, elemmaxx, elemminy, elemmaxy;
            if (forceXY) {
                elemminx = new Element("westBoundLongitude", ISO19139Namespaces.GMD).addContent(
                        new Element("Decimal", ISO19139Namespaces.GCO).setText("" + reprojected.getMinX()));
                elemmaxx = new Element("eastBoundLongitude", ISO19139Namespaces.GMD).addContent(
                        new Element("Decimal", ISO19139Namespaces.GCO).setText("" + reprojected.getMaxX()));
                elemminy = new Element("southBoundLatitude", ISO19139Namespaces.GMD).addContent(
                        new Element("Decimal", ISO19139Namespaces.GCO).setText("" + reprojected.getMinY()));
                elemmaxy = new Element("northBoundLatitude", ISO19139Namespaces.GMD).addContent(
                        new Element("Decimal", ISO19139Namespaces.GCO).setText("" + reprojected.getMaxY()));
            } else {
                elemminx = new Element("westBoundLongitude", ISO19139Namespaces.GMD).addContent(
                        new Element("Decimal", ISO19139Namespaces.GCO).setText("" + reprojected.getMinY()));
                elemmaxx = new Element("eastBoundLongitude", ISO19139Namespaces.GMD).addContent(
                        new Element("Decimal", ISO19139Namespaces.GCO).setText("" + reprojected.getMaxY()));
                elemminy = new Element("southBoundLatitude", ISO19139Namespaces.GMD).addContent(
                        new Element("Decimal", ISO19139Namespaces.GCO).setText("" + reprojected.getMinX()));
                elemmaxy = new Element("northBoundLatitude", ISO19139Namespaces.GMD).addContent(
                        new Element("Decimal", ISO19139Namespaces.GCO).setText("" + reprojected.getMaxX()));
            }
            elemRet.addContent(elemminx);
            elemRet.addContent(elemmaxx);
            elemRet.addContent(elemminy);
            elemRet.addContent(elemmaxy);

            ret = Xml.getString(elemRet);

        } catch (Throwable e) {
        }

        return ret;
    }

    public static String geomToBbox(Object geom) {
        String ret = "";
        try {
            String gml = (String) geom;

            Element geomElement = Xml.loadString(gml, false);
            String srs = geomElement.getAttributeValue("srsName");
            CoordinateReferenceSystem geomSrs = DefaultGeographicCRS.WGS84;
            if (srs != null && !(srs.equals("")))
                geomSrs = CRS.decode(srs);

            Parser parser = new Parser(new GMLConfiguration());
            MultiPolygon jts = parseGml(parser, gml);

            // if we have an srs and its not WGS84 then transform to WGS84
            if (!CRS.equalsIgnoreMetadata(geomSrs, DefaultGeographicCRS.WGS84)) {
                MathTransform tform = CRS.findMathTransform(geomSrs, DefaultGeographicCRS.WGS84);
                jts = (MultiPolygon) JTS.transform(jts, tform);
            }

            final Envelope envelope = jts.getEnvelopeInternal();
            return String.format(Locale.US, "%f|%f|%f|%f", envelope.getMinX(), envelope.getMinY(),
                    envelope.getMaxX(), envelope.getMaxY());
        } catch (Throwable e) {
        }

        return ret;
    }

    /**
     * Retrieve a metadata record. Use this function only
     * to retrieve records visible for current user. This
     * does not make any security checks.
     */
    public static Node getRecord(String uuid) {
        ApplicationContext applicationContext = ApplicationContextHolder.get();
        DataManager dataManager = applicationContext.getBean(DataManager.class);
        try {
            String id = dataManager.getMetadataId(uuid);
            if (id != null) {
                Element metadata = dataManager.getMetadata(id);

                DOMOutputter outputter = new DOMOutputter();
                return outputter.output(new Document(metadata));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * @param formula    Math expression to evaluate
     * @param parameters List of parameters and values used in the expression
     *                   to evaluate. PARAM_KEY1=VALUE|PARAM_KEY2=VALUE
     * @return
     */
    public static Double evaluate(String formula, String parameters) {
        try {
            // Tokenize parameter
            Map<String, Double> variables = new HashMap();
            Arrays.stream(parameters.split("\\|")).forEach(e -> {
                String[] tokens = e.trim().split("=");
                if (tokens.length == 2) {
                    variables.put(tokens[0], Double.valueOf(tokens[1].trim()));
                }
            });
            Expression e = new ExpressionBuilder(formula).variables(variables.keySet()).build()
                    .setVariables(variables);

            return e.evaluate();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static String getSiteUrl() {
        ServiceContext context = ServiceContext.get();
        String baseUrl = "";
        if (context != null)
            baseUrl = context.getBaseUrl();

        SettingInfo si = new SettingInfo();
        return si.getSiteUrl() + (!baseUrl.startsWith("/") ? "/" : "") + baseUrl;
    }

    public static String getLanguage() {
        ServiceContext context = ServiceContext.get();
        if (context != null) {
            return context.getLanguage();
        } else {
            return "eng";
        }
    }

    public static String encodeForJavaScript(String str) {
        return DefaultEncoder.getInstance().encodeForJavaScript(str);
    }

    public static String encodeForHTML(String str) {
        return DefaultEncoder.getInstance().encodeForHTML(str);
    }

    public static String md5Hex(String str) {
        return org.apache.commons.codec.digest.DigestUtils.md5Hex(str);
    }

    public static String encodeForURL(String str) {
        try {
            return DefaultEncoder.getInstance().encodeForURL(str);
        } catch (EncodingException ex) {
            ex.printStackTrace();
            return str;
        }
    }

    /**
     *  To get the xml content of an url
     *  It supports the usage of a proxy
    * @param surl
    * @return
     */
    public static Node getUrlContent(String surl) {

        Node res = null;
        InputStream is = null;

        ServiceContext context = ServiceContext.get();

        try {
            URL url = new URL(surl);
            URLConnection conn = Lib.net.setupProxy(context, url);

            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = dbf.newDocumentBuilder();

            is = conn.getInputStream();
            res = db.parse(is);

        } catch (Throwable e) {
            Log.error(Geonet.GEONETWORK, "Failed fetching url: " + surl, e);
        } finally {
            IOUtils.closeQuietly(is);
        }

        return res;
    }

    public static String decodeURLParameter(String str) {
        try {
            return java.net.URLDecoder.decode(str, "UTF-8");
        } catch (Exception ex) {
            ex.printStackTrace();
            return str;
        }
    }

    private static final Random RANDOM = new Random();

    public static String randomId() {
        return "N" + RANDOM.nextInt(Integer.MAX_VALUE);
    }

    public static String getMax(Object values) {
        String[] strings = values.toString().split(" ");
        String max = "";

        for (int i = 0; i < strings.length; i++) {
            String val = strings[i];
            if (val.compareTo(max) > 0) {
                max = val;
            }
        }
        return max;
    }

    private static final Cache<String, Boolean> URL_VALIDATION_CACHE;

    static {
        URL_VALIDATION_CACHE = CacheBuilder.<String, Boolean>newBuilder().maximumSize(100000)
                .expireAfterAccess(25, TimeUnit.HOURS).build();
    }

    public static boolean validateURL(final String urlString) throws ExecutionException {
        return URL_VALIDATION_CACHE.get(urlString, new Callable<Boolean>() {
            @Override
            public Boolean call() throws Exception {
                try {
                    return (Integer.parseInt(getUrlStatus(urlString)) / 100 == 2);
                } catch (Exception e) {
                    return false;
                }
            }
        });
    }

    /**
     * Utility method to retrieve the thesaurus dir from xsl processes.
     *
     * Usage:
     *
     *    <xsl:stylesheet
     *      xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
     *      ...
     *      xmlns:java="java:org.fao.geonet.util.XslUtil" ...>
     *
     *     <xsl:variable name="thesauriDir" select="java:getThesaurusDir()"/>
     *
     * @return Thesaurus directory
     */
    public static String getThesaurusDir() {
        ApplicationContext applicationContext = ApplicationContextHolder.get();
        ThesaurusManager thesaurusManager = applicationContext.getBean(ThesaurusManager.class);

        return thesaurusManager.getThesauriDirectory().toString();
    }
}