eu.europa.esig.dss.tsl.service.TSLParser.java Source code

Java tutorial

Introduction

Here is the source code for eu.europa.esig.dss.tsl.service.TSLParser.java

Source

/**
 * DSS - Digital Signature Services
 * Copyright (C) 2015 European Commission, provided under the CEF programme
 *
 * This file is part of the "DSS - Digital Signature Services" project.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
package eu.europa.esig.dss.tsl.service;

import java.io.InputStream;
import java.io.Serializable;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;

import javax.security.auth.x500.X500Principal;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.datatype.XMLGregorianCalendar;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;

import eu.europa.esig.dss.DSSException;
import eu.europa.esig.dss.DSSUtils;
import eu.europa.esig.dss.tsl.CompositeCondition;
import eu.europa.esig.dss.tsl.Condition;
import eu.europa.esig.dss.tsl.CriteriaListCondition;
import eu.europa.esig.dss.tsl.KeyUsageCondition;
import eu.europa.esig.dss.tsl.MatchingCriteriaIndicator;
import eu.europa.esig.dss.tsl.PolicyIdCondition;
import eu.europa.esig.dss.tsl.TSLConditionsForQualifiers;
import eu.europa.esig.dss.tsl.TSLParserResult;
import eu.europa.esig.dss.tsl.TSLPointer;
import eu.europa.esig.dss.tsl.TSLService;
import eu.europa.esig.dss.tsl.TSLServiceExtension;
import eu.europa.esig.dss.tsl.TSLServiceProvider;
import eu.europa.esig.dss.x509.CertificateToken;
import eu.europa.esig.jaxb.ecc.CriteriaListType;
import eu.europa.esig.jaxb.ecc.KeyUsageBitType;
import eu.europa.esig.jaxb.ecc.KeyUsageType;
import eu.europa.esig.jaxb.ecc.PoliciesListType;
import eu.europa.esig.jaxb.ecc.QualificationElementType;
import eu.europa.esig.jaxb.ecc.QualificationsType;
import eu.europa.esig.jaxb.ecc.QualifierType;
import eu.europa.esig.jaxb.ecc.QualifiersType;
import eu.europa.esig.jaxb.tsl.AnyType;
import eu.europa.esig.jaxb.tsl.DigitalIdentityListType;
import eu.europa.esig.jaxb.tsl.DigitalIdentityType;
import eu.europa.esig.jaxb.tsl.ExtensionType;
import eu.europa.esig.jaxb.tsl.ExtensionsListType;
import eu.europa.esig.jaxb.tsl.InternationalNamesType;
import eu.europa.esig.jaxb.tsl.MultiLangNormStringType;
import eu.europa.esig.jaxb.tsl.NextUpdateType;
import eu.europa.esig.jaxb.tsl.NonEmptyMultiLangURIType;
import eu.europa.esig.jaxb.tsl.NonEmptyURIListType;
import eu.europa.esig.jaxb.tsl.ObjectFactory;
import eu.europa.esig.jaxb.tsl.OtherTSLPointerType;
import eu.europa.esig.jaxb.tsl.PostalAddressType;
import eu.europa.esig.jaxb.tsl.ServiceHistoryInstanceType;
import eu.europa.esig.jaxb.tsl.ServiceHistoryType;
import eu.europa.esig.jaxb.tsl.TSPInformationType;
import eu.europa.esig.jaxb.tsl.TSPServiceInformationType;
import eu.europa.esig.jaxb.tsl.TSPServiceType;
import eu.europa.esig.jaxb.tsl.TSPServicesListType;
import eu.europa.esig.jaxb.tsl.TSPType;
import eu.europa.esig.jaxb.tsl.TrustServiceProviderListType;
import eu.europa.esig.jaxb.tsl.TrustStatusListType;
import eu.europa.esig.jaxb.xades.IdentifierType;
import eu.europa.esig.jaxb.xades.ObjectIdentifierType;

/**
 * This class allows to parse a TSL from JAXB object to DTO's. It can be executed as a Callable
 */
public class TSLParser implements Callable<TSLParserResult> {

    private static final Logger logger = LoggerFactory.getLogger(TSLParser.class);

    private static final String TSL_MIME_TYPE = "application/vnd.etsi.tsl+xml";

    private static final JAXBContext jaxbContext;

    private InputStream inputStream;

    static {
        try {
            jaxbContext = JAXBContext.newInstance(ObjectFactory.class, eu.europa.esig.jaxb.ecc.ObjectFactory.class);
        } catch (JAXBException e) {
            throw new DSSException("Unable to initialize JaxB : " + e.getMessage(), e);
        }
    }

    public TSLParser(InputStream inputStream) {
        this.inputStream = inputStream;
    }

    @Override
    @SuppressWarnings("unchecked")
    public TSLParserResult call() throws Exception {
        try {
            Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
            JAXBElement<TrustStatusListType> jaxbElement = (JAXBElement<TrustStatusListType>) unmarshaller
                    .unmarshal(inputStream);
            TrustStatusListType trustStatusList = jaxbElement.getValue();
            return getTslModel(trustStatusList);
        } catch (Exception e) {
            throw new DSSException("Unable to parse inputstream : " + e.getMessage(), e);
        }
    }

    private TSLParserResult getTslModel(TrustStatusListType tsl) {
        TSLParserResult tslModel = new TSLParserResult();
        tslModel.setTerritory(getTerritory(tsl));
        tslModel.setSequenceNumber(getSequenceNumber(tsl));
        tslModel.setIssueDate(getIssueDate(tsl));
        tslModel.setNextUpdateDate(getNextUpdate(tsl));
        tslModel.setDistributionPoints(getDistributionPoints(tsl));
        tslModel.setPointers(getMachineProcessableTSLPointers(tsl));
        tslModel.setServiceProviders(getServiceProviders(tsl));
        return tslModel;
    }

    private int getSequenceNumber(TrustStatusListType tsl) {
        BigInteger tslSequenceNumber = tsl.getSchemeInformation().getTSLSequenceNumber();
        if (tslSequenceNumber != null) {
            return tslSequenceNumber.intValue();
        }
        return -1;
    }

    private String getTerritory(TrustStatusListType tsl) {
        return tsl.getSchemeInformation().getSchemeTerritory();
    }

    private Date getIssueDate(TrustStatusListType tsl) {
        XMLGregorianCalendar gregorianCalendar = tsl.getSchemeInformation().getListIssueDateTime();
        return convertToDate(gregorianCalendar);
    }

    private Date getNextUpdate(TrustStatusListType tsl) {
        NextUpdateType nextUpdate = tsl.getSchemeInformation().getNextUpdate();
        if (nextUpdate != null) {
            return convertToDate(nextUpdate.getDateTime());
        }
        return null;
    }

    private List<String> getDistributionPoints(TrustStatusListType tsl) {
        NonEmptyURIListType distributionPoints = tsl.getSchemeInformation().getDistributionPoints();
        if (distributionPoints != null) {
            return distributionPoints.getURI();
        }
        return new ArrayList<String>();
    }

    private Date convertToDate(XMLGregorianCalendar gregorianCalendar) {
        if (gregorianCalendar != null) {
            GregorianCalendar toGregorianCalendar = gregorianCalendar.toGregorianCalendar();
            if (toGregorianCalendar != null) {
                return toGregorianCalendar.getTime();
            }
        }
        return null;
    }

    private List<TSLPointer> getMachineProcessableTSLPointers(TrustStatusListType tsl) {
        List<TSLPointer> list = new ArrayList<TSLPointer>();
        List<TSLPointer> tslPointers = getTSLPointers(tsl);
        if (CollectionUtils.isNotEmpty(tslPointers)) {
            for (TSLPointer tslPointer : tslPointers) {
                if (TSL_MIME_TYPE.equals(tslPointer.getMimeType())) {
                    list.add(tslPointer);
                }
            }
        }
        return list;
    }

    private List<TSLPointer> getTSLPointers(TrustStatusListType tsl) {
        List<TSLPointer> list = new ArrayList<TSLPointer>();
        if ((tsl.getSchemeInformation() != null) && (tsl.getSchemeInformation().getPointersToOtherTSL() != null)) {
            List<OtherTSLPointerType> pointers = tsl.getSchemeInformation().getPointersToOtherTSL()
                    .getOtherTSLPointer();
            for (OtherTSLPointerType otherTSLPointerType : pointers) {
                list.add(getPointerInfos(otherTSLPointerType));
            }
        }
        return list;
    }

    private TSLPointer getPointerInfos(OtherTSLPointerType otherTSLPointerType) {
        TSLPointer pointer = new TSLPointer();
        pointer.setUrl(otherTSLPointerType.getTSLLocation());
        pointer.setPotentialSigners(getPotentialSigners(otherTSLPointerType));
        fillPointerTerritoryAndMimeType(otherTSLPointerType, pointer);
        return pointer;
    }

    private void fillPointerTerritoryAndMimeType(OtherTSLPointerType otherTSLPointerType, TSLPointer pointer) {
        List<Serializable> textualInformationOrOtherInformation = otherTSLPointerType.getAdditionalInformation()
                .getTextualInformationOrOtherInformation();
        if (CollectionUtils.isNotEmpty(textualInformationOrOtherInformation)) {
            Map<String, String> properties = new HashMap<String, String>();
            for (Serializable serializable : textualInformationOrOtherInformation) {
                if (serializable instanceof AnyType) {
                    AnyType anyInfo = (AnyType) serializable;
                    for (Object content : anyInfo.getContent()) {
                        if (content instanceof JAXBElement) {
                            @SuppressWarnings("rawtypes")
                            JAXBElement jaxbElement = (JAXBElement) content;
                            properties.put(jaxbElement.getName().toString(), jaxbElement.getValue().toString());
                        } else if (content instanceof Element) {
                            Element element = (Element) content;
                            properties.put("{" + element.getNamespaceURI() + "}" + element.getLocalName(),
                                    element.getTextContent());
                        }
                    }
                }
            }
            pointer.setMimeType(properties.get("{http://uri.etsi.org/02231/v2/additionaltypes#}MimeType"));
            pointer.setTerritory(properties.get("{http://uri.etsi.org/02231/v2#}SchemeTerritory"));
        }
    }

    private List<CertificateToken> getPotentialSigners(OtherTSLPointerType otherTSLPointerType) {
        List<CertificateToken> list = new ArrayList<CertificateToken>();
        if (otherTSLPointerType.getServiceDigitalIdentities() != null) {
            List<DigitalIdentityListType> serviceDigitalIdentity = otherTSLPointerType.getServiceDigitalIdentities()
                    .getServiceDigitalIdentity();
            extractCertificates(serviceDigitalIdentity, list);
        }
        return list;
    }

    private void extractCertificates(List<DigitalIdentityListType> serviceDigitalIdentity,
            List<CertificateToken> result) {
        for (DigitalIdentityListType digitalIdentityListType : serviceDigitalIdentity) {
            List<CertificateToken> certificates = extractCertificates(digitalIdentityListType);
            if (CollectionUtils.isNotEmpty(certificates)) {
                result.addAll(certificates);
            }
        }
    }

    private List<CertificateToken> extractCertificates(DigitalIdentityListType digitalIdentityListType) {
        List<CertificateToken> certificates = new ArrayList<CertificateToken>();
        List<DigitalIdentityType> digitalIds = digitalIdentityListType.getDigitalId();
        for (DigitalIdentityType digitalId : digitalIds) {
            if (digitalId.getX509Certificate() != null) {
                try {
                    CertificateToken certificate = DSSUtils.loadCertificate(digitalId.getX509Certificate());
                    certificates.add(certificate);
                } catch (Exception e) {
                    logger.warn("Unable to load certificate : " + e.getMessage(), e);
                }
            }
        }
        return certificates;
    }

    private List<X500Principal> extractX500Principals(DigitalIdentityListType digitalIdentityListType) {
        List<X500Principal> result = new ArrayList<X500Principal>();
        List<DigitalIdentityType> digitalIds = digitalIdentityListType.getDigitalId();
        for (DigitalIdentityType digitalId : digitalIds) {
            if (digitalId.getX509SubjectName() != null) {
                try {
                    X500Principal x500Principal = DSSUtils.getX500Principal(digitalId.getX509SubjectName());
                    result.add(x500Principal);
                } catch (Exception e) {
                    logger.warn("Unable to load X500Principal : " + e.getMessage());
                }
            }
        }
        return result;
    }

    private List<TSLServiceProvider> getServiceProviders(TrustStatusListType tsl) {
        List<TSLServiceProvider> serviceProviders = new ArrayList<TSLServiceProvider>();
        TrustServiceProviderListType trustServiceProviderList = tsl.getTrustServiceProviderList();
        if ((trustServiceProviderList != null)
                && (CollectionUtils.isNotEmpty(trustServiceProviderList.getTrustServiceProvider()))) {
            for (TSPType tsp : trustServiceProviderList.getTrustServiceProvider()) {
                serviceProviders.add(getServiceProvider(tsp));
            }
        }
        return serviceProviders;
    }

    private TSLServiceProvider getServiceProvider(TSPType tsp) {
        TSLServiceProvider serviceProvider = new TSLServiceProvider();
        TSPInformationType tspInformation = tsp.getTSPInformation();
        if (tspInformation != null) {
            serviceProvider.setName(getEnglishOrFirst(tspInformation.getTSPName()));
            serviceProvider.setTradeName(getEnglishOrFirst(tspInformation.getTSPTradeName()));
            serviceProvider.setPostalAddress(getPostalAddress(tspInformation));
            serviceProvider.setElectronicAddress(getElectronicAddress(tspInformation));
            serviceProvider.setServices(getServices(tsp.getTSPServices()));
        }
        return serviceProvider;
    }

    private List<TSLService> getServices(TSPServicesListType tspServices) {
        List<TSLService> services = new ArrayList<TSLService>();
        if ((tspServices != null) && CollectionUtils.isNotEmpty(tspServices.getTSPService())) {
            Date previousStartDate = null;
            for (TSPServiceType tslService : tspServices.getTSPService()) {
                if (tslService.getServiceInformation() != null) {
                    TSLService service = getService(tslService.getServiceInformation());
                    previousStartDate = service.getStartDate();
                    services.add(service);
                }
                ServiceHistoryType serviceHistory = tslService.getServiceHistory();
                if ((serviceHistory != null)
                        && CollectionUtils.isNotEmpty(serviceHistory.getServiceHistoryInstance())) {
                    for (ServiceHistoryInstanceType serviceHistoryInstance : serviceHistory
                            .getServiceHistoryInstance()) {
                        TSLService service = getService(serviceHistoryInstance, previousStartDate);
                        previousStartDate = service.getStartDate();
                        services.add(service);
                    }
                }
            }
        }
        return services;
    }

    private TSLService getService(TSPServiceInformationType serviceInfo) {
        TSLService service = new TSLService();
        service.setName(getEnglishOrFirst(serviceInfo.getServiceName()));
        service.setStatus(serviceInfo.getServiceStatus());
        service.setStartDate(convertToDate(serviceInfo.getStatusStartingTime()));
        service.setType(serviceInfo.getServiceTypeIdentifier());
        service.setCertificateUrls(extractCertificatesUrls(serviceInfo));
        service.setCertificates(extractCertificates(serviceInfo.getServiceDigitalIdentity()));
        service.setX500Principals(extractX500Principals(serviceInfo.getServiceDigitalIdentity()));
        service.setExtensions(extractExtensions(serviceInfo.getServiceInformationExtensions()));
        return service;
    }

    private List<String> extractCertificatesUrls(TSPServiceInformationType serviceInfo) {
        Set<String> certificateUrls = new HashSet<String>();
        if ((serviceInfo.getSchemeServiceDefinitionURI() != null)
                && CollectionUtils.isNotEmpty(serviceInfo.getSchemeServiceDefinitionURI().getURI())) {
            List<NonEmptyMultiLangURIType> uris = serviceInfo.getSchemeServiceDefinitionURI().getURI();
            for (NonEmptyMultiLangURIType uri : uris) {
                String value = uri.getValue();
                if (isCertificateURI(value)) {
                    certificateUrls.add(value);
                }
            }
        }
        return new ArrayList<String>(certificateUrls);
    }

    private boolean isCertificateURI(String value) {
        return StringUtils.endsWithIgnoreCase(value, ".crt");
    }

    private TSLService getService(ServiceHistoryInstanceType serviceHistory, Date endDate) {
        TSLService service = new TSLService();
        service.setName(getEnglishOrFirst(serviceHistory.getServiceName()));
        service.setStatus(serviceHistory.getServiceStatus());
        service.setType(serviceHistory.getServiceTypeIdentifier());
        service.setStartDate(convertToDate(serviceHistory.getStatusStartingTime()));
        service.setEndDate(endDate);
        service.setCertificateUrls(new ArrayList<String>());
        service.setCertificates(extractCertificates(serviceHistory.getServiceDigitalIdentity()));
        service.setX500Principals(extractX500Principals(serviceHistory.getServiceDigitalIdentity()));
        service.setExtensions(extractExtensions(serviceHistory.getServiceInformationExtensions()));
        return service;
    }

    @SuppressWarnings("rawtypes")
    private List<TSLServiceExtension> extractExtensions(ExtensionsListType serviceInformationExtensions) {
        if ((serviceInformationExtensions != null)
                && CollectionUtils.isNotEmpty(serviceInformationExtensions.getExtension())) {
            List<TSLServiceExtension> extensions = new ArrayList<TSLServiceExtension>();
            for (ExtensionType extensionType : serviceInformationExtensions.getExtension()) {
                List<Object> content = extensionType.getContent();
                if (CollectionUtils.isNotEmpty(content)) {
                    List<TSLConditionsForQualifiers> conditionsForQualifiers = new ArrayList<TSLConditionsForQualifiers>();
                    for (Object object : content) {
                        if (object instanceof JAXBElement) {
                            JAXBElement jaxbElement = (JAXBElement) object;
                            Object objectValue = jaxbElement.getValue();
                            if (objectValue instanceof QualificationsType) {
                                QualificationsType qt = (QualificationsType) jaxbElement.getValue();
                                if ((qt != null) && CollectionUtils.isNotEmpty(qt.getQualificationElement())) {
                                    for (QualificationElementType qualificationElement : qt
                                            .getQualificationElement()) {
                                        List<String> qualifiers = extractQualifiers(qualificationElement);
                                        Condition condition = getCondition(qualificationElement.getCriteriaList());
                                        if (CollectionUtils.isNotEmpty(qualifiers) && (condition != null)) {
                                            conditionsForQualifiers
                                                    .add(new TSLConditionsForQualifiers(qualifiers, condition));
                                        }
                                    }
                                }
                            }
                        }
                    }
                    if (CollectionUtils.isNotEmpty(conditionsForQualifiers)) {
                        TSLServiceExtension extension = new TSLServiceExtension();
                        extension.setCritical(extensionType.isCritical());
                        extension.setConditionsForQualifiers(conditionsForQualifiers);
                        extensions.add(extension);
                    }
                }
            }
            return extensions;
        }
        return null;
    }

    private List<String> extractQualifiers(QualificationElementType qualificationElement) {
        List<String> qualifiers = new ArrayList<String>();
        QualifiersType qualifiersType = qualificationElement.getQualifiers();
        if ((qualifiersType != null) && CollectionUtils.isNotEmpty(qualifiersType.getQualifier())) {
            for (QualifierType qualitierType : qualifiersType.getQualifier()) {
                qualifiers.add(qualitierType.getUri());
            }
        }
        return qualifiers;
    }

    private Condition getCondition(CriteriaListType criteriaList) {
        MatchingCriteriaIndicator matchingCriteriaIndicator = MatchingCriteriaIndicator
                .valueOf(criteriaList.getAssert());
        CompositeCondition condition = new CriteriaListCondition(matchingCriteriaIndicator);
        addKeyUsageConditionsIfPresent(criteriaList.getKeyUsage(), condition);
        addPolicyIdConditionsIfPresent(criteriaList.getPolicySet(), condition);
        addCriteriaListConditionsIfPresent(criteriaList.getCriteriaList(), condition);
        return condition;
    }

    private void addPolicyIdConditionsIfPresent(List<PoliciesListType> policySet, CompositeCondition condition) {
        if (CollectionUtils.isNotEmpty(policySet)) {
            for (PoliciesListType policiesListType : policySet) {
                for (ObjectIdentifierType oidType : policiesListType.getPolicyIdentifier()) {
                    IdentifierType identifier = oidType.getIdentifier();
                    String id = identifier.getValue();

                    // ES TSL :  <ns4:Identifier Qualifier="OIDAsURN">urn:oid:1.3.6.1.4.1.36035.1.3.1</ns4:Identifier>
                    if (id.indexOf(':') >= 0) {
                        id = id.substring(id.lastIndexOf(':') + 1);
                    }

                    condition.addChild(new PolicyIdCondition(id));
                }
            }
        }
    }

    private void addKeyUsageConditionsIfPresent(List<KeyUsageType> keyUsages, CompositeCondition condition) {
        if (CollectionUtils.isNotEmpty(keyUsages)) {
            for (KeyUsageType keyUsageType : keyUsages) {
                for (KeyUsageBitType keyUsageBit : keyUsageType.getKeyUsageBit()) {
                    condition.addChild(new KeyUsageCondition(keyUsageBit.getName(), keyUsageBit.isValue()));
                }
            }
        }
    }

    private void addCriteriaListConditionsIfPresent(List<CriteriaListType> criteriaList,
            CompositeCondition condition) {
        if (CollectionUtils.isNotEmpty(criteriaList)) {
            CompositeCondition compositeConditions = new CompositeCondition();
            for (CriteriaListType criteriaListType : criteriaList) {
                compositeConditions.addChild(getCondition(criteriaListType));
            }
            condition.addChild(compositeConditions);
        }
    }

    private String getPostalAddress(TSPInformationType tspInformation) {
        PostalAddressType a = null;
        if (tspInformation.getTSPAddress() == null) {
            return null;
        }
        for (PostalAddressType c : tspInformation.getTSPAddress().getPostalAddresses().getPostalAddress()) {
            if ("en".equalsIgnoreCase(c.getLang())) {
                a = c;
                break;
            }
        }
        if (a == null) {
            a = tspInformation.getTSPAddress().getPostalAddresses().getPostalAddress().get(0);
        }

        StringBuffer sb = new StringBuffer();
        if (StringUtils.isNotEmpty(a.getStreetAddress())) {
            sb.append(a.getStreetAddress());
            sb.append(", ");
        }
        if (StringUtils.isNotEmpty(a.getPostalCode())) {
            sb.append(a.getPostalCode());
            sb.append(", ");
        }
        if (StringUtils.isNotEmpty(a.getLocality())) {
            sb.append(a.getLocality());
            sb.append(", ");
        }
        if (StringUtils.isNotEmpty(a.getStateOrProvince())) {
            sb.append(a.getStateOrProvince());
            sb.append(", ");
        }
        if (StringUtils.isNotEmpty(a.getCountryName())) {
            sb.append(a.getCountryName());
        }
        return sb.toString();
    }

    private String getElectronicAddress(TSPInformationType tspInformation) {
        if (tspInformation.getTSPAddress().getElectronicAddress() == null) {
            return null;
        }
        return tspInformation.getTSPAddress().getElectronicAddress().getURI().get(0).getValue();
    }

    private String getEnglishOrFirst(InternationalNamesType names) {
        if (names == null) {
            return null;
        }
        for (MultiLangNormStringType s : names.getName()) {
            if ("en".equalsIgnoreCase(s.getLang())) {
                return s.getValue();
            }
        }
        return names.getName().get(0).getValue();
    }

}