import java.io.IOException;
import java.util.Vector;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.rpc.Call;
import javax.xml.rpc.Service;
import javax.xml.rpc.ServiceFactory;

import org.apache.axis.message.SOAPBodyElement;
import org.apache.xml.serialize.LineSeparator;
import org.apache.xml.serialize.OutputFormat;
import org.apache.xml.serialize.XMLSerializer;
import org.jaxen.JaxenException;
import org.jaxen.SimpleNamespaceContext;
import org.jaxen.dom.DOMXPath;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * Beispiel f�r ein Login-Servlet, das von MOA-ID-AUTH �ber einen Redirect aufgerufen wird.
 * Es werden demonstriert:
 * - Parameter�bergabe von MOA-ID-AUTH
 * - Aufruf des MOA-ID-AUTH Web Service zum Abholen der Anmeldedaten �ber das Apache Axis Framework
 * - Parsen der Anmeldedaten mittels der XPath Engine "Jaxen"
 * - Speichern der Anmeldedaten in der HTTPSession
 * - Redirect auf die eigentliche Startseite der OA
 * 
 * @author Paul Ivancsics
 */
public class LoginServletExample extends HttpServlet {

  // Web Service QName und Endpoint
  private static final QName SERVICE_QNAME = new QName("GetAuthenticationData");
  private static final String ENDPOINT =
    "http://localhost:8080/moa-id-auth/services/GetAuthenticationData";
  // NamespaceContext f�r Jaxen
  private static SimpleNamespaceContext NS_CONTEXT;
  static {
    NS_CONTEXT = new SimpleNamespaceContext();
    NS_CONTEXT.addNamespace("saml", "urn:oasis:names:tc:SAML:1.0:assertion");
    NS_CONTEXT.addNamespace("samlp", "urn:oasis:names:tc:SAML:1.0:protocol");
    NS_CONTEXT.addNamespace("pr", "http://reference.e-government.gv.at/namespace/persondata/20020228#");
  }
  
  /**
   * Servlet wird von MOA-ID-AUTH nach erfolgter Authentisierung �ber ein Redirect aufgerufen.
   */
  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException {

    // Parameter "Target" und "SAMLArtifact" aus dem Redirect von MOA-ID-AUTH lesen
    String target = req.getParameter("Target");
    String samlArtifact = req.getParameter("SAMLArtifact");
    
    try {
      // DOMBuilder instanzieren
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      factory.setNamespaceAware(true);
      DocumentBuilder builder = factory.newDocumentBuilder();
  
      // <samlp:Request> zusammenstellen und in einen DOM-Baum umwandeln
      String samlRequest =
        "<?xml version=\"1.0\" encoding=\"UTF-8\"?><samlp:Request IssueInstant=\"2003-01-01T00:00:00+02:00\" MajorVersion=\"1\" MinorVersion=\"0\" RequestID=\"12345678901234567890\" xmlns:samlp=\"urn:oasis:names:tc:SAML:1.0:protocol\"><samlp:AssertionArtifact>"
          + samlArtifact
          + "</samlp:AssertionArtifact></samlp:Request>";
      Document root_request = builder.parse(new ByteArrayInputStream(samlRequest.getBytes()));
  
      // Neues SOAPBodyElement anlegen und mit dem DOM-Baum f�llen
      SOAPBodyElement body = new SOAPBodyElement(root_request.getDocumentElement());
      SOAPBodyElement[] params = new SOAPBodyElement[] { body };
  
      // AXIS-Service f�r Aufruf von MOA-ID-AUTH instanzieren
      Service service = ServiceFactory.newInstance().createService(SERVICE_QNAME);

      // Axis-Call erzeugen und mit Endpoint verkn�pfen
      Call call = service.createCall();
      call.setTargetEndpointAddress(ENDPOINT);
  
      // Call aufrufen und die Antwort speichern
      System.out.println("Calling MOA-ID-AUTH ...");
      Vector responses = (Vector) call.invoke(params);
  
      // erstes BodyElement auslesen
      SOAPBodyElement response = (SOAPBodyElement) responses.get(0);
  
      // <samlp:Response> als DOM-Baum holen
      Document responseDocument = response.getAsDocument();
      Element samlResponse = responseDocument.getDocumentElement();
  
      // <samlp:Response> auf System.out ausgeben
      System.out.println("Response received:");
      OutputFormat format = new OutputFormat((Document) responseDocument);
      format.setLineSeparator(LineSeparator.Windows);
      format.setIndenting(true);
      format.setLineWidth(0);
      XMLSerializer serializer = new XMLSerializer(System.out, format);
      serializer.asDOMSerializer();
      serializer.serialize(responseDocument);

      // <samlp:StatusCode> auslesen
      Attr statusCodeAttr = (Attr)getNode(samlResponse, "/samlp:Response/samlp:Status/samlp:StatusCode/@Value");
      String samlStatusCode = statusCodeAttr.getValue();
      System.out.println("StatusCode: " + samlStatusCode);
      
      // <saml:Assertion> auslesen
      if ("samlp:Success".equals(samlStatusCode)) {
        Element samlAssertion = (Element)getNode(samlResponse, "/samlp:Response/saml:Assertion");
        
        // FamilyName aus der <saml:Assertion> parsen 
        Node familyNameNode = getNode(samlAssertion, "//saml:AttributeStatement/saml:Attribute[@AttributeName=\"PersonData\"]/saml:AttributeValue/pr:Person/pr:Name/pr:FamilyName");
        String familyName = getText(familyNameNode);
        System.out.println("Family name: " + familyName);
        
        // weitere Anmeldedaten aus der <saml:Assertion> parsen 
        // ...
        
        // Anmeldedaten und Target in der HTTPSession speichern
        HttpSession session = req.getSession();
        session.setAttribute("UserFamilyName", familyName);
        session.setAttribute("Geschaeftsbereich", target);

        // weitere Anmeldedaten in der HTTPSession speichern
        // ...
        
        // Redirect auf die eigentliche Startseite
        resp.sendRedirect("/index.jsp");
      }
    }
    catch (Exception ex) {
      ex.printStackTrace();
    }
  }
  /** Returns the first node matching an XPath expression. */
  private static Node getNode(Node contextNode, String xpathExpression) throws JaxenException {
    DOMXPath xpath = new DOMXPath(xpathExpression);
    xpath.setNamespaceContext(NS_CONTEXT);
    return (Node) xpath.selectSingleNode(contextNode);
  }
  /** Returns the text that a node contains. */
  public static String getText(Node node) {
    if (!node.hasChildNodes()) {
      return "";
    }

    StringBuffer result = new StringBuffer();
    NodeList list = node.getChildNodes();
    for (int i = 0; i < list.getLength(); i++) {
      Node subnode = list.item(i);
      if (subnode.getNodeType() == Node.TEXT_NODE) {
        result.append(subnode.getNodeValue());
      } else if (subnode.getNodeType() == Node.CDATA_SECTION_NODE) {
        result.append(subnode.getNodeValue());
      } else if (subnode.getNodeType() == Node.ENTITY_REFERENCE_NODE) {
        // Recurse into the subtree for text
        // (and ignore comments)
        result.append(getText(subnode));
      }
    }
    return result.toString();
  }
}