/**
   * Builds an IdP List out of the idpEntityNames
   *
   * @param idpEntityNames The IdPs Entity IDs to include in the IdP List, no list is created when
   *     null
   * @param serviceURI The binding service for an IdP for a specific binding. Should be null if
   *     there is more than one IdP in the list or if the destination IdP is not known in advance.
   * @return an IdP List or null when idpEntityNames is null
   */
  protected IDPList buildIDPList(Set<String> idpEntityNames, SingleSignOnService serviceURI) {

    if (idpEntityNames == null) {
      return null;
    }

    SAMLObjectBuilder<IDPEntry> idpEntryBuilder =
        (SAMLObjectBuilder<IDPEntry>) builderFactory.getBuilder(IDPEntry.DEFAULT_ELEMENT_NAME);
    SAMLObjectBuilder<IDPList> idpListBuilder =
        (SAMLObjectBuilder<IDPList>) builderFactory.getBuilder(IDPList.DEFAULT_ELEMENT_NAME);
    IDPList idpList = idpListBuilder.buildObject();

    for (String entityID : idpEntityNames) {
      IDPEntry idpEntry = idpEntryBuilder.buildObject();
      idpEntry.setProviderID(entityID);
      idpList.getIDPEntrys().add(idpEntry);

      // The service URI would be null if the SP does not know in advance
      // to which IdP the request is sent to.
      if (serviceURI != null) {
        idpEntry.setLoc(serviceURI.getLocation());
      }
    }

    return idpList;
  }
  protected void sendLogoutResponse(Status status, SAMLMessageContext context)
      throws MetadataProviderException, SAMLException, MessageEncodingException {

    SAMLObjectBuilder<LogoutResponse> responseBuilder =
        (SAMLObjectBuilder<LogoutResponse>)
            builderFactory.getBuilder(LogoutResponse.DEFAULT_ELEMENT_NAME);
    LogoutResponse logoutResponse = responseBuilder.buildObject();

    IDPSSODescriptor idpDescriptor = SAMLUtil.getIDPDescriptor(metadata, context.getPeerEntityId());
    SPSSODescriptor spDescriptor = (SPSSODescriptor) context.getLocalEntityRoleMetadata();
    String binding = SAMLUtil.getLogoutBinding(idpDescriptor, spDescriptor);
    SingleLogoutService logoutService = SAMLUtil.getLogoutServiceForBinding(idpDescriptor, binding);

    logoutResponse.setID(generateID());
    logoutResponse.setIssuer(getIssuer(context.getLocalEntityId()));
    logoutResponse.setVersion(SAMLVersion.VERSION_20);
    logoutResponse.setIssueInstant(new DateTime());
    logoutResponse.setInResponseTo(context.getInboundSAMLMessageId());
    logoutResponse.setDestination(logoutService.getLocation());
    logoutResponse.setStatus(status);

    context.setCommunicationProfileId(getProfileIdentifier());
    context.setOutboundMessage(logoutResponse);
    context.setOutboundSAMLMessage(logoutResponse);
    context.setPeerEntityEndpoint(logoutService);

    context.setPeerEntityId(idpDescriptor.getID());
    context.setPeerEntityRoleMetadata(idpDescriptor);

    boolean signMessage = context.getPeerExtendedMetadata().isRequireLogoutResponseSigned();
    sendMessage(context, signMessage);
  }
  /**
   * Fills the request with required AuthNContext according to selected options.
   *
   * @param request request to fill
   * @param options options driving generation of the element
   */
  protected void buildAuthnContext(AuthnRequest request, WebSSOProfileOptions options) {

    Collection<String> contexts = options.getAuthnContexts();
    if (contexts != null && contexts.size() > 0) {

      SAMLObjectBuilder<RequestedAuthnContext> builder =
          (SAMLObjectBuilder<RequestedAuthnContext>)
              builderFactory.getBuilder(RequestedAuthnContext.DEFAULT_ELEMENT_NAME);
      RequestedAuthnContext authnContext = builder.buildObject();
      authnContext.setComparison(options.getAuthnContextComparison());

      for (String context : contexts) {

        SAMLObjectBuilder<AuthnContextClassRef> contextRefBuilder =
            (SAMLObjectBuilder<AuthnContextClassRef>)
                builderFactory.getBuilder(AuthnContextClassRef.DEFAULT_ELEMENT_NAME);
        AuthnContextClassRef authnContextClassRef = contextRefBuilder.buildObject();
        authnContextClassRef.setAuthnContextClassRef(context);
        authnContext.getAuthnContextClassRefs().add(authnContextClassRef);
      }

      request.setRequestedAuthnContext(authnContext);
      log.debug("ajoute RequestedAuthnContext=" + request.toString());
    }
  }
  /**
   * Fills the request with required AuthNContext according to selected options.
   *
   * @param request request to fill
   * @param options options driving generation of the element
   */
  protected void builNameIDPolicy(AuthnRequest request, WebSSOProfileOptions options) {

    if (options.getNameID() != null) {
      SAMLObjectBuilder<NameIDPolicy> builder =
          (SAMLObjectBuilder<NameIDPolicy>)
              builderFactory.getBuilder(NameIDPolicy.DEFAULT_ELEMENT_NAME);
      NameIDPolicy nameIDPolicy = builder.buildObject();
      nameIDPolicy.setFormat(options.getNameID());
      nameIDPolicy.setAllowCreate(options.isAllowCreate());
      nameIDPolicy.setSPNameQualifier(getSPNameQualifier());
      request.setNameIDPolicy(nameIDPolicy);
    }
  }
  /**
   * Fills the request with information about scoping, including IDP in the scope IDP List.
   *
   * @param request request to fill
   * @param serviceURI destination to send the request to
   * @param options options driving generation of the element, contains list of allowed IDPs
   */
  protected void buildScoping(
      AuthnRequest request, SingleSignOnService serviceURI, WebSSOProfileOptions options) {

    if (options.isIncludeScoping() != null && options.isIncludeScoping()) {

      Set<String> idpEntityNames = options.getAllowedIDPs();
      IDPList idpList = buildIDPList(idpEntityNames, serviceURI);
      SAMLObjectBuilder<Scoping> scopingBuilder =
          (SAMLObjectBuilder<Scoping>) builderFactory.getBuilder(Scoping.DEFAULT_ELEMENT_NAME);
      Scoping scoping = scopingBuilder.buildObject();
      scoping.setIDPList(idpList);
      scoping.setProxyCount(options.getProxyCount());
      request.setScoping(scoping);
    }
  }
  /** {@inheritDoc} */
  public Attribute encode(BaseAttribute attribute) throws AttributeEncodingException {
    Attribute samlAttribute = attributeBuilder.buildObject();
    samlAttribute.setAttributeName(getAttributeName());
    samlAttribute.setAttributeNamespace(getNamespace());
    samlAttribute
        .getAttributeValues()
        .addAll(encodeAttributeValues(AttributeValue.DEFAULT_ELEMENT_NAME, attribute));

    List<XMLObject> attributeValues = samlAttribute.getAttributeValues();
    if (attributeValues == null || attributeValues.isEmpty()) {
      log.debug(
          "Unable to encode {} attribute.  It does not contain any values", attribute.getId());
      return null;
    }

    return samlAttribute;
  }
  /**
   * Builds a name ID. The provided value is the textual content of the NameIdentifier. If a {@link
   * #nameIdQualifier} is not null it is used as the NameIdentifier's name qualifier, otherwise the
   * attribute issuer's entity id is used.
   *
   * @param nameIdValue value of the NameIdentifier
   * @param resolutionContext current resolution context
   * @return the constructed NameIdentifier
   */
  protected NameIdentifier buildNameId(
      String nameIdValue, ShibbolethResolutionContext resolutionContext) {
    NameIdentifier nameId = nameIdBuilder.buildObject();
    nameId.setNameIdentifier(nameIdValue);

    if (nameIdFormat != null) {
      nameId.setFormat(nameIdFormat);
    }

    if (nameIdQualifier != null) {
      nameId.setNameQualifier(nameIdQualifier);
    } else {
      nameId.setNameQualifier(resolutionContext.getAttributeRequestContext().getLocalEntityId());
    }

    return nameId;
  }
  /**
   * Returns logout request message ready to be sent to the IDP.
   *
   * @param context message context
   * @param credential information about assertions used to log current user in
   * @param bindingService service used to deliver the request
   * @return logoutRequest to be sent to IDP
   * @throws SAMLException error creating the message
   * @throws MetadataProviderException error retrieving metadata
   */
  protected LogoutRequest getLogoutRequest(
      SAMLMessageContext context, SAMLCredential credential, Endpoint bindingService)
      throws SAMLException, MetadataProviderException {

    SAMLObjectBuilder<LogoutRequest> builder =
        (SAMLObjectBuilder<LogoutRequest>)
            builderFactory.getBuilder(LogoutRequest.DEFAULT_ELEMENT_NAME);
    LogoutRequest request = builder.buildObject();
    buildCommonAttributes(context.getLocalEntityId(), request, bindingService);

    // Add session indexes
    SAMLObjectBuilder<SessionIndex> sessionIndexBuilder =
        (SAMLObjectBuilder<SessionIndex>)
            builderFactory.getBuilder(SessionIndex.DEFAULT_ELEMENT_NAME);
    for (AuthnStatement statement : credential.getAuthenticationAssertion().getAuthnStatements()) {
      SessionIndex index = sessionIndexBuilder.buildObject();
      index.setSessionIndex(statement.getSessionIndex());
      request.getSessionIndexes().add(index);
    }

    if (request.getSessionIndexes().size() == 0) {
      throw new SAMLException("No session indexes to logout user for were found");
    }

    SAMLObjectBuilder<NameID> nameIDBuilder =
        (SAMLObjectBuilder<NameID>) builderFactory.getBuilder(NameID.DEFAULT_ELEMENT_NAME);
    NameID nameID = nameIDBuilder.buildObject();
    nameID.setFormat(credential.getNameID().getFormat());
    nameID.setNameQualifier(credential.getNameID().getNameQualifier());
    nameID.setSPNameQualifier(credential.getNameID().getSPNameQualifier());
    nameID.setSPProvidedID(credential.getNameID().getSPProvidedID());
    nameID.setValue(credential.getNameID().getValue());
    request.setNameID(nameID);

    return request;
  }
  /**
   * Returns AuthnRequest SAML message to be used to demand authentication from an IDP described
   * using idpEntityDescriptor, with an expected response to the assertionConsumer address.
   *
   * @param context message context
   * @param options preferences of message creation
   * @param assertionConsumer assertion consumer where the IDP should respond
   * @param bindingService service used to deliver the request
   * @return authnRequest ready to be sent to IDP
   * @throws SAMLException error creating the message
   * @throws MetadataProviderException error retreiving metadata
   */
  protected AuthnRequest getAuthnRequest(
      SAMLMessageContext context,
      WebSSOProfileOptions options,
      AssertionConsumerService assertionConsumer,
      SingleSignOnService bindingService)
      throws SAMLException, MetadataProviderException {

    SAMLObjectBuilder<AuthnRequest> builder =
        (SAMLObjectBuilder<AuthnRequest>)
            builderFactory.getBuilder(AuthnRequest.DEFAULT_ELEMENT_NAME);
    AuthnRequest request = builder.buildObject();

    request.setIsPassive(options.getPassive());
    request.setForceAuthn(options.getForceAuthN());
    request.setProviderName(options.getProviderName());
    request.setVersion(SAMLVersion.VERSION_20);
    if (options.getIncludeEidas()) {
      // ne sert à rien
      //            request.setAssertionConsumerServiceURL(options.getIssuer());
      //            IssuerBuilder issuerBuilder = new IssuerBuilder();
      //            Issuer issuer = issuerBuilder.buildObject();
      //            issuer.setFormat(NAME_ISSUER_FORMAT_EIDAS);
      //            log.debug("issuer="+options.getIssuer());
      //            issuer.setValue(options.getIssuer());
      //            request.setIssuer(issuer);
      NameIDPolicy nameIDPolicy = new NameIDPolicyBuilder().buildObject();
      nameIDPolicy.setFormat(NAME_POLICY_FORMAT_EIDAS);
      nameIDPolicy.setAllowCreate(true);
      request.setNameIDPolicy(nameIDPolicy);
      QName eidas = new QName("xmlns:eidas", "http://eidas.europa.eu/saml-extensions");
      request.getNamespaceManager().registerAttributeName(eidas);
      Extensions extEidas =
          new ExtensionsBuilder()
              .buildObject("urn:oasis:names:tc:SAML:2.0:protocol", "Extensions", "saml2p");
      // Extensions extEidas = new EidasExtensions();
      Collection<String> colAttr = options.getEidasAttributes();
      // XSAnyBuilder raBuild = new XSAnyBuilder();
      // <eidas:SPType>public</eidas:SPType>
      SPType pub =
          new SPTypeBuilder()
              .buildObject("http://eidas.europa.eu/saml-extensions", "SPType", "eidas");
      // pub.setTextContent(EIDAS_PUBLIC);
      pub.setSPType(EIDAS_PUBLIC);
      // XSAny attrs = new XSAnyBuilder().buildObject("http://eidas.europa.eu/saml-extensions",
      // "RequestedAttributes", "eidas");
      extEidas.getUnknownXMLObjects().add(pub);
      // XSAnyBuilder anyBuilder = (XSAnyBuilder)
      // Configuration.getBuilderFactory().getBuilder(XSAny.TYPE_NAME);
      String resAttrs =
          "<eidas:RequestedAttributes xmlns:eidas=\"http://eidas.europa.eu/saml-extensions\">";
      for (String attr : colAttr) {
        resAttrs += oneAttribute(attr);
      }
      resAttrs += "</eidas:RequestedAttributes>";
      log.debug("resAttrs=" + resAttrs);
      EidasExtensionConfiguration eidasExt = new EidasExtensionConfiguration();
      eidasExt.configureExtension();
      SAMLSchemaBuilder.addExtensionSchema("/schema/saml_eidas_extension.xsd");
      BasicParserPool ppMgr = new BasicParserPool();
      ppMgr.setNamespaceAware(true);
      try {
        ppMgr.setSchema(SAMLSchemaBuilder.getSAML11Schema());
      } catch (SAXException ex) {
        log.error("Erreur schema=" + ex);
        return null;
      }
      InputStream is = new ByteArrayInputStream(resAttrs.getBytes());
      Document domAttrsRaq = null;
      try {
        domAttrsRaq = ppMgr.parse(is);
      } catch (XMLParserException e) {
        log.error("Erreur dom=" + e);
        return null;
      }
      if (domAttrsRaq == null) {
        log.error("Erreur dom vide");
        return null;
      }
      RequestedAttributesUnmarshaller unMars = new RequestedAttributesUnmarshaller();
      XMLObject attrs = null;
      try {
        attrs = unMars.unmarshall(domAttrsRaq.getDocumentElement());
      } catch (UnmarshallingException e) {
        System.err.println("Erreur unMarsh error=" + e);
      }

      extEidas.getUnknownXMLObjects().add(attrs);
      request.setExtensions(extEidas);
    }
    buildCommonAttributes(context.getLocalEntityId(), request, bindingService);

    buildScoping(request, bindingService, options);
    builNameIDPolicy(request, options);
    buildAuthnContext(request, options);
    buildReturnAddress(request, assertionConsumer);

    return request;
  }