XAdESDocumentPlacementService.java
package io.mersel.dss.signer.api.services.signature.xades;
import java.io.IOException;
import java.io.StringReader;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.springframework.stereotype.Service;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import io.mersel.dss.signer.api.constants.XmlConstants;
import io.mersel.dss.signer.api.exceptions.SignatureException;
import io.mersel.dss.signer.api.models.enums.DocumentType;
import io.mersel.dss.signer.api.util.xml.SecureXmlFactories;
/**
* XML belgelerinde imza elemanlarını yerleştiren servis.
* Belge tipine özgü yerleştirme mantığını yönetir (UBL, e-Arşiv, HrXml vb.).
*/
@Service
public class XAdESDocumentPlacementService {
/**
* İmza elemanını belge tipine göre uygun konuma yerleştirir.
*
* @param document Ana XML belgesi
* @param signatureElement Yerleştirilecek imza elemanı
* @param documentType Belge tipi
*/
public void placeSignatureElement(Document document,
Element signatureElement,
DocumentType documentType) {
// İmzayı mevcut üst elemanından kaldır
Node parent = signatureElement.getParentNode();
if (parent != null) {
parent.removeChild(signatureElement);
}
// Belge tipine göre hedef konumu belirle
Node target = resolveTargetNode(document, documentType);
// İmzayı import et ve ekle
Node importedSignature = document.importNode(signatureElement, true);
target.appendChild(importedSignature);
}
/**
* İmzanın yerleştirileceği hedef node'u çözümler.
*/
private Node resolveTargetNode(Document document, DocumentType documentType) {
Node target = null;
switch (documentType) {
case UblDocument:
target = findUblExtensionContent(document);
break;
case EArchiveReport:
target = findEArchiveHeader(document);
break;
case EBiletReport:
target = findEBiletHeader(document);
break;
case HrXml:
target = findHrXmlSignatureContainer(document);
break;
default:
// Diğer belge tipleri için kök elemana yerleştir
break;
}
// Yedek olarak belge köküne yerleştir
if (target == null) {
target = document.getDocumentElement();
}
return target;
}
/**
* UBL belgelerinde imzadan ÖNCE UBLExtensions yapısının mevcut olmasını sağlar.
* Mevcut yapıyı katman katman kontrol eder ve sadece eksik olanı ekler:
* <ul>
* <li>UBLExtensions bile yoksa -> tüm yapıyı kök elemanın başına ekler</li>
* <li>UBLExtensions var ama UBLExtension yoksa -> UBLExtension/ExtensionContent
* ekler</li>
* <li>UBLExtension var ama ExtensionContent yoksa -> sadece ExtensionContent
* ekler</li>
* </ul>
* İmza, belgenin canonical formu üzerinden hesaplanır; bu yüzden yapı imzalama
* öncesinde eklenmelidir.
*
* @param document UBL belgesi
* @return ExtensionContent zaten varsa false, yeni eklendiyse true
*/
public boolean ensureUblExtensionContentExists(Document document) {
if (getFirstElementByTagNameNS(document, XmlConstants.NS_UBL_EXTENSION, "ExtensionContent") != null) {
return false;
}
ensureUblExtensionStructure(document);
return true;
}
/**
* UBL ExtensionContent elemanını bulur. Yoksa eksik katmanları tamamlayarak
* ExtensionContent döner.
* <p>
* DOM createElementNS ile oluşturma, imza canonicalization'da farklı çıktı
* ürettiği için string parse kullanılıyor.
*/
private Node findUblExtensionContent(Document document) {
Node target = getFirstElementByTagNameNS(document, XmlConstants.NS_UBL_EXTENSION, "ExtensionContent");
if (target != null) {
return target;
}
ensureUblExtensionStructure(document);
return getFirstElementByTagNameNS(document, XmlConstants.NS_UBL_EXTENSION, "ExtensionContent");
}
/**
* UBLExtensions/UBLExtension/ExtensionContent hiyerarşisini katman katman
* kontrol eder
* ve sadece eksik olan kısmı ekler.
*/
private void ensureUblExtensionStructure(Document document) {
String ns = XmlConstants.NS_UBL_EXTENSION;
Node ublExtensions = getFirstElementByTagNameNS(document, ns, "UBLExtensions");
if (ublExtensions == null) {
addFullUblExtensionsStructure(document);
return;
}
Node ublExtension = getFirstElementByTagNameNS(document, ns, "UBLExtension");
if (ublExtension == null) {
Node fragment = parseFragment(
"<ext:UBLExtension xmlns:ext=\"" + ns + "\">" +
"<ext:ExtensionContent></ext:ExtensionContent>" +
"</ext:UBLExtension>");
ublExtensions.insertBefore(
document.importNode(fragment, true),
ublExtensions.getFirstChild());
return;
}
Node extensionContent = getFirstElementByTagNameNS(document, ns, "ExtensionContent");
if (extensionContent == null) {
Node fragment = parseFragment(
"<ext:ExtensionContent xmlns:ext=\"" + ns + "\"></ext:ExtensionContent>");
ublExtension.insertBefore(
document.importNode(fragment, true),
ublExtension.getFirstChild());
}
}
/**
* Hiçbir UBLExtensions yapısı olmadığında tüm hiyerarşiyi kök elemanın başına
* ekler.
*/
private void addFullUblExtensionsStructure(Document document) {
Node fragment = parseFragment(
"<ext:UBLExtensions xmlns:ext=\"" + XmlConstants.NS_UBL_EXTENSION + "\">" +
"<ext:UBLExtension>" +
"<ext:ExtensionContent></ext:ExtensionContent>" +
"</ext:UBLExtension>" +
"</ext:UBLExtensions>");
Element root = document.getDocumentElement();
Node imported = document.importNode(fragment, true);
Node firstChild = root.getFirstChild();
if (firstChild != null) {
root.insertBefore(imported, firstChild);
} else {
root.appendChild(imported);
}
}
/**
* XML string'ini parse edip kök elemanını döner.
* DOM createElementNS yerine string parse kullanılır çünkü
* canonicalization sırasında namespace declaration sıralaması farklılık
* yaratabilir.
*/
private Node parseFragment(String xml) {
try {
DocumentBuilderFactory dbf = SecureXmlFactories.newDocumentBuilderFactory();
DocumentBuilder db = dbf.newDocumentBuilder();
return db.parse(new InputSource(new StringReader(xml))).getDocumentElement();
} catch (ParserConfigurationException | SAXException | IOException e) {
throw new SignatureException("UBL_EXTENSION_ERROR",
"UBLExtensions yapısı eklenirken hata oluştu", e);
}
}
/**
* e-Arşiv Raporu başlık elemanını bulur.
*
* @throws IllegalArgumentException Başlık elemanı bulunamazsa
*/
private Node findEArchiveHeader(Document document) {
Node target = getFirstElementByTagNameNS(document, XmlConstants.NS_EARSIV, "baslik");
if (target == null) {
throw new IllegalArgumentException(
String.format("e-Arşiv rapor belgesi için 'baslik' elemanı bulunamadı. " +
"Beklenen namespace: %s", XmlConstants.NS_EARSIV));
}
return target;
}
/**
* HrXml imza konteynırını bulur.
*
* @throws IllegalArgumentException ApplicationArea elemanı bulunamazsa
*/
private Node findHrXmlSignatureContainer(Document document) {
// ApplicationArea'yı namespace ile bul
Node applicationArea = getFirstElementByTagNameNS(document, XmlConstants.NS_OAGIS, "ApplicationArea");
// Namespace olmadan fallback
if (applicationArea == null) {
applicationArea = getFirstElementByTagName(document, "ApplicationArea");
}
if (applicationArea == null) {
throw new IllegalArgumentException(
String.format("HrXml belgesi için 'ApplicationArea' elemanı bulunamadı. " +
"Beklenen namespace: %s", XmlConstants.NS_OAGIS));
}
// ApplicationArea içinde Signature node'unu bul (namespace ile)
Node signatureNode = getFirstChildElementNS(applicationArea, XmlConstants.NS_OAGIS, "Signature");
// Namespace olmadan fallback
if (signatureNode == null) {
signatureNode = getFirstChildElement(applicationArea, "Signature");
}
return signatureNode != null ? signatureNode : applicationArea;
}
/**
* e-Bilet Raporu başlık elemanını bulur.
*
* @throws IllegalArgumentException Başlık elemanı bulunamazsa
*/
private Node findEBiletHeader(Document document) {
Node target = getFirstElementByTagNameNS(document, XmlConstants.NS_BILET, "baslik");
if (target == null) {
throw new IllegalArgumentException(
String.format("e-Bilet rapor belgesi için 'baslik' elemanı bulunamadı. " +
"Beklenen namespace: %s", XmlConstants.NS_BILET));
}
return target;
}
private Node getFirstElementByTagName(Document document, String tagName) {
NodeList nodeList = document.getElementsByTagName(tagName);
return (nodeList != null && nodeList.getLength() > 0) ? nodeList.item(0) : null;
}
private Node getFirstElementByTagNameNS(Document document, String namespace, String localName) {
NodeList nodeList = document.getElementsByTagNameNS(namespace, localName);
return (nodeList != null && nodeList.getLength() > 0) ? nodeList.item(0) : null;
}
private Node getFirstChildElement(Node parent, String tagName) {
NodeList nodeList = ((Element) parent).getElementsByTagName(tagName);
return (nodeList != null && nodeList.getLength() > 0) ? nodeList.item(0) : null;
}
private Node getFirstChildElementNS(Node parent, String namespace, String localName) {
NodeList nodeList = ((Element) parent).getElementsByTagNameNS(namespace, localName);
return (nodeList != null && nodeList.getLength() > 0) ? nodeList.item(0) : null;
}
}