sfm = sf.getNamespacePrefixMapping();
+ if (sfm != null) {
+ namespaceMap.putAll(sfm);
+ }
+ }
+ xo.setSaveSuggestedPrefixes(namespaceMap);
+ xo.setUseDefaultNamespace();
+
+ LOG.log(POILogger.DEBUG, "output signed Office OpenXML document");
+
+ /*
+ * Copy the original OOXML content to the signed OOXML package. During
+ * copying some files need to changed.
+ */
+ OPCPackage pkg = this.getOfficeOpenXMLDocument();
+
+ PackagePartName sigPartName, sigsPartName;
+ try {
+ //
+ sigPartName = PackagingURIHelper.createPartName("/_xmlsignatures/sig1.xml");
+ //
+ sigsPartName = PackagingURIHelper.createPartName("/_xmlsignatures/origin.sigs");
+ } catch (InvalidFormatException e) {
+ throw new IOException(e);
+ }
+
+ String sigContentType = "application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml";
+ PackagePart sigPart = pkg.getPart(sigPartName);
+ if (sigPart == null) {
+ sigPart = pkg.createPart(sigPartName, sigContentType);
+ }
+
+ OutputStream os = sigPart.getOutputStream();
+ sigDoc.save(os, xo);
+ os.close();
+
+ String sigsContentType = "application/vnd.openxmlformats-package.digital-signature-origin";
+ PackagePart sigsPart = pkg.getPart(sigsPartName);
+ if (sigsPart == null) {
+ // touch empty marker file
+ sigsPart = pkg.createPart(sigsPartName, sigsContentType);
+ }
+
+ PackageRelationshipCollection relCol = pkg.getRelationshipsByType(PackageRelationshipTypes.DIGITAL_SIGNATURE_ORIGIN);
+ for (PackageRelationship pr : relCol) {
+ pkg.removeRelationship(pr.getId());
+ }
+ pkg.addRelationship(sigsPartName, TargetMode.INTERNAL, PackageRelationshipTypes.DIGITAL_SIGNATURE_ORIGIN);
+
+ sigsPart.addRelationship(sigPartName, TargetMode.INTERNAL, PackageRelationshipTypes.DIGITAL_SIGNATURE);
+ }
+}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/AddressDTO.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/AddressDTO.java
new file mode 100644
index 000000000..a16404631
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/AddressDTO.java
@@ -0,0 +1,51 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+/* ====================================================================
+ This product contains an ASLv2 licensed version of the OOXML signer
+ package from the eID Applet project
+ http://code.google.com/p/eid-applet/source/browse/trunk/README.txt
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.spi;
+
+import java.io.Serializable;
+import java.security.Identity;
+
+/**
+ * Address Data Transfer Object.
+ *
+ * @author Frank Cornelis
+ * @see Identity
+ *
+ */
+public class AddressDTO implements Serializable {
+
+ /*
+ * We implement serializable to allow this class to be used in distributed
+ * containers as defined in the Servlet v2.4 specification.
+ */
+
+ private static final long serialVersionUID = 1L;
+
+ public String streetAndNumber;
+
+ public String zip;
+
+ public String city;
+}
\ No newline at end of file
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/Constants.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/Constants.java
new file mode 100644
index 000000000..7c2caeb63
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/Constants.java
@@ -0,0 +1,30 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+/* ====================================================================
+ This product contains an ASLv2 licensed version of the OOXML signer
+ package from the eID Applet project
+ http://code.google.com/p/eid-applet/source/browse/trunk/README.txt
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.spi;
+
+public interface Constants {
+ String NamespaceSpecNS = "http://www.w3.org/2000/xmlns/";
+ String SignatureSpecNS = "http://www.w3.org/2000/09/xmldsig#";
+}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/DigestInfo.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/DigestInfo.java
new file mode 100644
index 000000000..2f7c58c33
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/DigestInfo.java
@@ -0,0 +1,56 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+/* ====================================================================
+ This product contains an ASLv2 licensed version of the OOXML signer
+ package from the eID Applet project
+ http://code.google.com/p/eid-applet/source/browse/trunk/README.txt
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.spi;
+
+import java.io.Serializable;
+
+import org.apache.poi.poifs.crypt.HashAlgorithm;
+
+/**
+ * Digest Information data transfer class.
+ */
+public class DigestInfo implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Main constructor.
+ *
+ * @param digestValue
+ * @param hashAlgo
+ * @param description
+ */
+ public DigestInfo(byte[] digestValue, HashAlgorithm hashAlgo, String description) {
+ this.digestValue = digestValue;
+ this.hashAlgo = hashAlgo;
+ this.description = description;
+ }
+
+ public final byte[] digestValue;
+
+ public final String description;
+
+ public final HashAlgorithm hashAlgo;
+}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/IdentityDTO.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/IdentityDTO.java
new file mode 100644
index 000000000..9cfa0aae2
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/IdentityDTO.java
@@ -0,0 +1,75 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+/* ====================================================================
+ This product contains an ASLv2 licensed version of the OOXML signer
+ package from the eID Applet project
+ http://code.google.com/p/eid-applet/source/browse/trunk/README.txt
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.spi;
+
+import java.io.Serializable;
+import java.util.GregorianCalendar;
+
+/**
+ * Identity Data Transfer Object.
+ *
+ * @author Frank Cornelis
+ *
+ */
+public class IdentityDTO implements Serializable {
+
+ /*
+ * We implement serializable to allow this class to be used in distributed
+ * containers as defined in the Servlet v2.4 specification.
+ */
+ private static final long serialVersionUID = 1L;
+
+ public String cardNumber;
+
+ public String chipNumber;
+
+ public GregorianCalendar cardValidityDateBegin;
+
+ public GregorianCalendar cardValidityDateEnd;
+
+ public String cardDeliveryMunicipality;
+
+ public String nationalNumber;
+
+ public String name;
+
+ public String firstName;
+
+ public String middleName;
+
+ public String nationality;
+
+ public String placeOfBirth;
+
+ public GregorianCalendar dateOfBirth;
+
+ public boolean male;
+
+ public boolean female;
+
+ public String nobleCondition;
+
+ public String duplicate;
+}
\ No newline at end of file
diff --git a/src/ooxml/java/org/apache/poi/util/MethodUtils.java b/src/ooxml/java/org/apache/poi/util/MethodUtils.java
new file mode 100644
index 000000000..c006c8546
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/util/MethodUtils.java
@@ -0,0 +1,1334 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.util;
+
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+
+/**
+ * Utility reflection methods focussed on methods in general rather than properties in particular.
+ *
+ * Known Limitations
+ * Accessing Public Methods In A Default Access Superclass
+ * There is an issue when invoking public methods contained in a default access superclass.
+ * Reflection locates these methods fine and correctly assigns them as public.
+ * However, an IllegalAccessException
is thrown if the method is invoked.
+ *
+ * MethodUtils
contains a workaround for this situation.
+ * It will attempt to call setAccessible
on this method.
+ * If this call succeeds, then the method can be invoked as normal.
+ * This call will only succeed when the application has sufficient security privilages.
+ * If this call fails then a warning will be logged and the method may fail.
+ *
+ * @author Craig R. McClanahan
+ * @author Ralph Schaer
+ * @author Chris Audley
+ * @author Rey François
+ * @author Gregor Raýman
+ * @author Jan Sorensen
+ * @author Robert Burrell Donkin
+ */
+
+public class MethodUtils {
+
+ // --------------------------------------------------------- Private Methods
+
+ /**
+ * Only log warning about accessibility work around once.
+ *
+ * Note that this is broken when this class is deployed via a shared
+ * classloader in a container, as the warning message will be emitted
+ * only once, not once per webapp. However making the warning appear
+ * once per webapp means having a map keyed by context classloader
+ * which introduces nasty memory-leak problems. As this warning is
+ * really optional we can ignore this problem; only one of the webapps
+ * will get the warning in its logs but that should be good enough.
+ */
+ private static boolean loggedAccessibleWarning = false;
+
+ /**
+ * Indicates whether methods should be cached for improved performance.
+ *
+ * Note that when this class is deployed via a shared classloader in
+ * a container, this will affect all webapps. However making this
+ * configurable per webapp would mean having a map keyed by context classloader
+ * which may introduce memory-leak problems.
+ */
+ private static boolean CACHE_METHODS = true;
+
+ /** An empty class array */
+ private static final Class[] EMPTY_CLASS_PARAMETERS = new Class[0];
+ /** An empty object array */
+ private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
+
+ // --------------------------------------------------------- Public Methods
+
+ /**
+ *
Invoke a named method whose parameter type matches the object type.
+ *
+ * The behaviour of this method is less deterministic
+ * than invokeExactMethod()
.
+ * It loops through all methods with names that match
+ * and then executes the first it finds with compatable parameters.
+ *
+ * This method supports calls to methods taking primitive parameters
+ * via passing in wrapping classes. So, for example, a Boolean
class
+ * would match a boolean
primitive.
+ *
+ * This is a convenient wrapper for
+ * {@link #invokeMethod(Object object,String methodName,Object [] args)}.
+ *
+ *
+ * @param object invoke method on this object
+ * @param methodName get method with this name
+ * @param arg use this argument
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeMethod(
+ Object object,
+ String methodName,
+ Object arg)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+
+ Object[] args = {arg};
+ return invokeMethod(object, methodName, args);
+
+ }
+
+
+ /**
+ * Invoke a named method whose parameter type matches the object type.
+ *
+ * The behaviour of this method is less deterministic
+ * than {@link #invokeExactMethod(Object object,String methodName,Object [] args)}.
+ * It loops through all methods with names that match
+ * and then executes the first it finds with compatable parameters.
+ *
+ * This method supports calls to methods taking primitive parameters
+ * via passing in wrapping classes. So, for example, a Boolean
class
+ * would match a boolean
primitive.
+ *
+ * This is a convenient wrapper for
+ * {@link #invokeMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}.
+ *
+ *
+ * @param object invoke method on this object
+ * @param methodName get method with this name
+ * @param args use these arguments - treat null as empty array
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeMethod(
+ Object object,
+ String methodName,
+ Object[] args)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+
+ if (args == null) {
+ args = EMPTY_OBJECT_ARRAY;
+ }
+ int arguments = args.length;
+ Class[] parameterTypes = new Class[arguments];
+ for (int i = 0; i < arguments; i++) {
+ parameterTypes[i] = args[i].getClass();
+ }
+ return invokeMethod(object, methodName, args, parameterTypes);
+
+ }
+
+
+ /**
+ * Invoke a named method whose parameter type matches the object type.
+ *
+ * The behaviour of this method is less deterministic
+ * than {@link
+ * #invokeExactMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}.
+ * It loops through all methods with names that match
+ * and then executes the first it finds with compatable parameters.
+ *
+ * This method supports calls to methods taking primitive parameters
+ * via passing in wrapping classes. So, for example, a Boolean
class
+ * would match a boolean
primitive.
+ *
+ *
+ * @param object invoke method on this object
+ * @param methodName get method with this name
+ * @param args use these arguments - treat null as empty array
+ * @param parameterTypes match these parameters - treat null as empty array
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeMethod(
+ Object object,
+ String methodName,
+ Object[] args,
+ Class[] parameterTypes)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+
+ if (parameterTypes == null) {
+ parameterTypes = EMPTY_CLASS_PARAMETERS;
+ }
+ if (args == null) {
+ args = EMPTY_OBJECT_ARRAY;
+ }
+
+ Method method = getMatchingAccessibleMethod(
+ object.getClass(),
+ methodName,
+ parameterTypes);
+ if (method == null) {
+ throw new NoSuchMethodException("No such accessible method: " +
+ methodName + "() on object: " + object.getClass().getName());
+ }
+ return method.invoke(object, args);
+ }
+
+
+ /**
+ * Invoke a method whose parameter type matches exactly the object
+ * type.
+ *
+ * This is a convenient wrapper for
+ * {@link #invokeExactMethod(Object object,String methodName,Object [] args)}.
+ *
+ *
+ * @param object invoke method on this object
+ * @param methodName get method with this name
+ * @param arg use this argument
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeExactMethod(
+ Object object,
+ String methodName,
+ Object arg)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+
+ Object[] args = {arg};
+ return invokeExactMethod(object, methodName, args);
+
+ }
+
+
+ /**
+ * Invoke a method whose parameter types match exactly the object
+ * types.
+ *
+ * This uses reflection to invoke the method obtained from a call to
+ * getAccessibleMethod()
.
+ *
+ * @param object invoke method on this object
+ * @param methodName get method with this name
+ * @param args use these arguments - treat null as empty array
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeExactMethod(
+ Object object,
+ String methodName,
+ Object[] args)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+ if (args == null) {
+ args = EMPTY_OBJECT_ARRAY;
+ }
+ int arguments = args.length;
+ Class[] parameterTypes = new Class[arguments];
+ for (int i = 0; i < arguments; i++) {
+ parameterTypes[i] = args[i].getClass();
+ }
+ return invokeExactMethod(object, methodName, args, parameterTypes);
+
+ }
+
+
+ /**
+ * Invoke a method whose parameter types match exactly the parameter
+ * types given.
+ *
+ * This uses reflection to invoke the method obtained from a call to
+ * getAccessibleMethod()
.
+ *
+ * @param object invoke method on this object
+ * @param methodName get method with this name
+ * @param args use these arguments - treat null as empty array
+ * @param parameterTypes match these parameters - treat null as empty array
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeExactMethod(
+ Object object,
+ String methodName,
+ Object[] args,
+ Class[] parameterTypes)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+
+ if (args == null) {
+ args = EMPTY_OBJECT_ARRAY;
+ }
+
+ if (parameterTypes == null) {
+ parameterTypes = EMPTY_CLASS_PARAMETERS;
+ }
+
+ Method method = getAccessibleMethod(
+ object.getClass(),
+ methodName,
+ parameterTypes);
+ if (method == null) {
+ throw new NoSuchMethodException("No such accessible method: " +
+ methodName + "() on object: " + object.getClass().getName());
+ }
+ return method.invoke(object, args);
+
+ }
+
+ /**
+ * Invoke a static method whose parameter types match exactly the parameter
+ * types given.
+ *
+ * This uses reflection to invoke the method obtained from a call to
+ * {@link #getAccessibleMethod(Class, String, Class[])}.
+ *
+ * @param objectClass invoke static method on this class
+ * @param methodName get method with this name
+ * @param args use these arguments - treat null as empty array
+ * @param parameterTypes match these parameters - treat null as empty array
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeExactStaticMethod(
+ Class objectClass,
+ String methodName,
+ Object[] args,
+ Class[] parameterTypes)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+
+ if (args == null) {
+ args = EMPTY_OBJECT_ARRAY;
+ }
+
+ if (parameterTypes == null) {
+ parameterTypes = EMPTY_CLASS_PARAMETERS;
+ }
+
+ Method method = getAccessibleMethod(
+ objectClass,
+ methodName,
+ parameterTypes);
+ if (method == null) {
+ throw new NoSuchMethodException("No such accessible method: " +
+ methodName + "() on class: " + objectClass.getName());
+ }
+ return method.invoke(null, args);
+
+ }
+
+ /**
+ * Invoke a named static method whose parameter type matches the object type.
+ *
+ * The behaviour of this method is less deterministic
+ * than {@link #invokeExactMethod(Object, String, Object[], Class[])}.
+ * It loops through all methods with names that match
+ * and then executes the first it finds with compatable parameters.
+ *
+ * This method supports calls to methods taking primitive parameters
+ * via passing in wrapping classes. So, for example, a Boolean
class
+ * would match a boolean
primitive.
+ *
+ * This is a convenient wrapper for
+ * {@link #invokeStaticMethod(Class objectClass,String methodName,Object [] args)}.
+ *
+ *
+ * @param objectClass invoke static method on this class
+ * @param methodName get method with this name
+ * @param arg use this argument
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeStaticMethod(
+ Class objectClass,
+ String methodName,
+ Object arg)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+
+ Object[] args = {arg};
+ return invokeStaticMethod (objectClass, methodName, args);
+
+ }
+
+
+ /**
+ * Invoke a named static method whose parameter type matches the object type.
+ *
+ * The behaviour of this method is less deterministic
+ * than {@link #invokeExactMethod(Object object,String methodName,Object [] args)}.
+ * It loops through all methods with names that match
+ * and then executes the first it finds with compatable parameters.
+ *
+ * This method supports calls to methods taking primitive parameters
+ * via passing in wrapping classes. So, for example, a Boolean
class
+ * would match a boolean
primitive.
+ *
+ * This is a convenient wrapper for
+ * {@link #invokeStaticMethod(Class objectClass,String methodName,Object [] args,Class[] parameterTypes)}.
+ *
+ *
+ * @param objectClass invoke static method on this class
+ * @param methodName get method with this name
+ * @param args use these arguments - treat null as empty array
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeStaticMethod(
+ Class objectClass,
+ String methodName,
+ Object[] args)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+
+ if (args == null) {
+ args = EMPTY_OBJECT_ARRAY;
+ }
+ int arguments = args.length;
+ Class[] parameterTypes = new Class[arguments];
+ for (int i = 0; i < arguments; i++) {
+ parameterTypes[i] = args[i].getClass();
+ }
+ return invokeStaticMethod (objectClass, methodName, args, parameterTypes);
+
+ }
+
+
+ /**
+ * Invoke a named static method whose parameter type matches the object type.
+ *
+ * The behaviour of this method is less deterministic
+ * than {@link
+ * #invokeExactStaticMethod(Class objectClass,String methodName,Object [] args,Class[] parameterTypes)}.
+ * It loops through all methods with names that match
+ * and then executes the first it finds with compatable parameters.
+ *
+ * This method supports calls to methods taking primitive parameters
+ * via passing in wrapping classes. So, for example, a Boolean
class
+ * would match a boolean
primitive.
+ *
+ *
+ * @param objectClass invoke static method on this class
+ * @param methodName get method with this name
+ * @param args use these arguments - treat null as empty array
+ * @param parameterTypes match these parameters - treat null as empty array
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeStaticMethod(
+ Class objectClass,
+ String methodName,
+ Object[] args,
+ Class[] parameterTypes)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+
+ if (parameterTypes == null) {
+ parameterTypes = EMPTY_CLASS_PARAMETERS;
+ }
+ if (args == null) {
+ args = EMPTY_OBJECT_ARRAY;
+ }
+
+ Method method = getMatchingAccessibleMethod(
+ objectClass,
+ methodName,
+ parameterTypes);
+ if (method == null) {
+ throw new NoSuchMethodException("No such accessible method: " +
+ methodName + "() on class: " + objectClass.getName());
+ }
+ return method.invoke(null, args);
+ }
+
+
+ /**
+ * Invoke a static method whose parameter type matches exactly the object
+ * type.
+ *
+ * This is a convenient wrapper for
+ * {@link #invokeExactStaticMethod(Class objectClass,String methodName,Object [] args)}.
+ *
+ *
+ * @param objectClass invoke static method on this class
+ * @param methodName get method with this name
+ * @param arg use this argument
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeExactStaticMethod(
+ Class objectClass,
+ String methodName,
+ Object arg)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+
+ Object[] args = {arg};
+ return invokeExactStaticMethod (objectClass, methodName, args);
+
+ }
+
+
+ /**
+ * Invoke a static method whose parameter types match exactly the object
+ * types.
+ *
+ * This uses reflection to invoke the method obtained from a call to
+ * {@link #getAccessibleMethod(Class, String, Class[])}.
+ *
+ * @param objectClass invoke static method on this class
+ * @param methodName get method with this name
+ * @param args use these arguments - treat null as empty array
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeExactStaticMethod(
+ Class objectClass,
+ String methodName,
+ Object[] args)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+ if (args == null) {
+ args = EMPTY_OBJECT_ARRAY;
+ }
+ int arguments = args.length;
+ Class[] parameterTypes = new Class[arguments];
+ for (int i = 0; i < arguments; i++) {
+ parameterTypes[i] = args[i].getClass();
+ }
+ return invokeExactStaticMethod(objectClass, methodName, args, parameterTypes);
+
+ }
+
+
+ /**
+ * Return an accessible method (that is, one that can be invoked via
+ * reflection) with given name and a single parameter. If no such method
+ * can be found, return null
.
+ * Basically, a convenience wrapper that constructs a Class
+ * array for you.
+ *
+ * @param clazz get method from this class
+ * @param methodName get method with this name
+ * @param parameterType taking this type of parameter
+ * @return The accessible method
+ */
+ public static Method getAccessibleMethod(
+ Class clazz,
+ String methodName,
+ Class parameterType) {
+
+ Class[] parameterTypes = {parameterType};
+ return getAccessibleMethod(clazz, methodName, parameterTypes);
+
+ }
+
+
+ /**
+ * Return an accessible method (that is, one that can be invoked via
+ * reflection) with given name and parameters. If no such method
+ * can be found, return null
.
+ * This is just a convenient wrapper for
+ * {@link #getAccessibleMethod(Method method)}.
+ *
+ * @param clazz get method from this class
+ * @param methodName get method with this name
+ * @param parameterTypes with these parameters types
+ * @return The accessible method
+ */
+ public static Method getAccessibleMethod(
+ Class clazz,
+ String methodName,
+ Class[] parameterTypes) {
+
+ try {
+ MethodDescriptor md = new MethodDescriptor(clazz, methodName, parameterTypes, true);
+ Method method = getAccessibleMethod
+ (clazz, clazz.getMethod(methodName, parameterTypes));
+ return method;
+ } catch (NoSuchMethodException e) {
+ return (null);
+ }
+
+ }
+
+
+ /**
+ * Return an accessible method (that is, one that can be invoked via
+ * reflection) that implements the specified Method. If no such method
+ * can be found, return null
.
+ *
+ * @param method The method that we wish to call
+ * @return The accessible method
+ */
+ public static Method getAccessibleMethod(Method method) {
+
+ // Make sure we have a method to check
+ if (method == null) {
+ return (null);
+ }
+
+ return getAccessibleMethod(method.getDeclaringClass(), method);
+
+ }
+
+
+
+ /**
+ * Return an accessible method (that is, one that can be invoked via
+ * reflection) that implements the specified Method. If no such method
+ * can be found, return null
.
+ *
+ * @param clazz The class of the object
+ * @param method The method that we wish to call
+ * @return The accessible method
+ */
+ public static Method getAccessibleMethod(Class clazz, Method method) {
+
+ // Make sure we have a method to check
+ if (method == null) {
+ return (null);
+ }
+
+ // If the requested method is not public we cannot call it
+ if (!Modifier.isPublic(method.getModifiers())) {
+ return (null);
+ }
+
+ boolean sameClass = true;
+ if (clazz == null) {
+ clazz = method.getDeclaringClass();
+ } else {
+ sameClass = clazz.equals(method.getDeclaringClass());
+ if (!method.getDeclaringClass().isAssignableFrom(clazz)) {
+ throw new IllegalArgumentException(clazz.getName() +
+ " is not assignable from " + method.getDeclaringClass().getName());
+ }
+ }
+
+ // If the class is public, we are done
+ if (Modifier.isPublic(clazz.getModifiers())) {
+ if (!sameClass && !Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
+ setMethodAccessible(method); // Default access superclass workaround
+ }
+ return (method);
+ }
+
+ String methodName = method.getName();
+ Class[] parameterTypes = method.getParameterTypes();
+
+ // Check the implemented interfaces and subinterfaces
+ method =
+ getAccessibleMethodFromInterfaceNest(clazz,
+ methodName,
+ parameterTypes);
+
+ // Check the superclass chain
+ if (method == null) {
+ method = getAccessibleMethodFromSuperclass(clazz,
+ methodName,
+ parameterTypes);
+ }
+
+ return (method);
+
+ }
+
+
+ // -------------------------------------------------------- Private Methods
+
+ /**
+ * Return an accessible method (that is, one that can be invoked via
+ * reflection) by scanning through the superclasses. If no such method
+ * can be found, return null
.
+ *
+ * @param clazz Class to be checked
+ * @param methodName Method name of the method we wish to call
+ * @param parameterTypes The parameter type signatures
+ */
+ private static Method getAccessibleMethodFromSuperclass
+ (Class clazz, String methodName, Class[] parameterTypes) {
+
+ Class parentClazz = clazz.getSuperclass();
+ while (parentClazz != null) {
+ if (Modifier.isPublic(parentClazz.getModifiers())) {
+ try {
+ return parentClazz.getMethod(methodName, parameterTypes);
+ } catch (NoSuchMethodException e) {
+ return null;
+ }
+ }
+ parentClazz = parentClazz.getSuperclass();
+ }
+ return null;
+ }
+
+ /**
+ * Return an accessible method (that is, one that can be invoked via
+ * reflection) that implements the specified method, by scanning through
+ * all implemented interfaces and subinterfaces. If no such method
+ * can be found, return null
.
+ *
+ * There isn't any good reason why this method must be private.
+ * It is because there doesn't seem any reason why other classes should
+ * call this rather than the higher level methods.
+ *
+ * @param clazz Parent class for the interfaces to be checked
+ * @param methodName Method name of the method we wish to call
+ * @param parameterTypes The parameter type signatures
+ */
+ private static Method getAccessibleMethodFromInterfaceNest
+ (Class clazz, String methodName, Class[] parameterTypes) {
+
+ Method method = null;
+
+ // Search up the superclass chain
+ for (; clazz != null; clazz = clazz.getSuperclass()) {
+
+ // Check the implemented interfaces of the parent class
+ Class[] interfaces = clazz.getInterfaces();
+ for (int i = 0; i < interfaces.length; i++) {
+
+ // Is this interface public?
+ if (!Modifier.isPublic(interfaces[i].getModifiers())) {
+ continue;
+ }
+
+ // Does the method exist on this interface?
+ try {
+ method = interfaces[i].getDeclaredMethod(methodName,
+ parameterTypes);
+ } catch (NoSuchMethodException e) {
+ /* Swallow, if no method is found after the loop then this
+ * method returns null.
+ */
+ }
+ if (method != null) {
+ return method;
+ }
+
+ // Recursively check our parent interfaces
+ method =
+ getAccessibleMethodFromInterfaceNest(interfaces[i],
+ methodName,
+ parameterTypes);
+ if (method != null) {
+ return method;
+ }
+
+ }
+
+ }
+
+ // If we found a method return it
+ if (method != null) {
+ return (method);
+ }
+
+ // We did not find anything
+ return (null);
+
+ }
+
+ /**
+ * Find an accessible method that matches the given name and has compatible parameters.
+ * Compatible parameters mean that every method parameter is assignable from
+ * the given parameters.
+ * In other words, it finds a method with the given name
+ * that will take the parameters given.
+ *
+ *
This method is slightly undeterminstic since it loops
+ * through methods names and return the first matching method.
+ *
+ * This method is used by
+ * {@link
+ * #invokeMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}.
+ *
+ *
This method can match primitive parameter by passing in wrapper classes.
+ * For example, a Boolean
will match a primitive boolean
+ * parameter.
+ *
+ * @param clazz find method in this class
+ * @param methodName find method with this name
+ * @param parameterTypes find method with compatible parameters
+ * @return The accessible method
+ */
+ public static Method getMatchingAccessibleMethod(
+ Class clazz,
+ String methodName,
+ Class[] parameterTypes) {
+ // trace logging
+ Log log = LogFactory.getLog(MethodUtils.class);
+ if (log.isTraceEnabled()) {
+ log.trace("Matching name=" + methodName + " on " + clazz);
+ }
+ MethodDescriptor md = new MethodDescriptor(clazz, methodName, parameterTypes, false);
+
+ // see if we can find the method directly
+ // most of the time this works and it's much faster
+ try {
+ Method method = clazz.getMethod(methodName, parameterTypes);
+ if (log.isTraceEnabled()) {
+ log.trace("Found straight match: " + method);
+ log.trace("isPublic:" + Modifier.isPublic(method.getModifiers()));
+ }
+
+ setMethodAccessible(method); // Default access superclass workaround
+
+ return method;
+
+ } catch (NoSuchMethodException e) { /* SWALLOW */ }
+
+ // search through all methods
+ int paramSize = parameterTypes.length;
+ Method bestMatch = null;
+ Method[] methods = clazz.getMethods();
+ float bestMatchCost = Float.MAX_VALUE;
+ float myCost = Float.MAX_VALUE;
+ for (int i = 0, size = methods.length; i < size ; i++) {
+ if (methods[i].getName().equals(methodName)) {
+ // log some trace information
+ if (log.isTraceEnabled()) {
+ log.trace("Found matching name:");
+ log.trace(methods[i]);
+ }
+
+ // compare parameters
+ Class[] methodsParams = methods[i].getParameterTypes();
+ int methodParamSize = methodsParams.length;
+ if (methodParamSize == paramSize) {
+ boolean match = true;
+ for (int n = 0 ; n < methodParamSize; n++) {
+ if (log.isTraceEnabled()) {
+ log.trace("Param=" + parameterTypes[n].getName());
+ log.trace("Method=" + methodsParams[n].getName());
+ }
+ if (!isAssignmentCompatible(methodsParams[n], parameterTypes[n])) {
+ if (log.isTraceEnabled()) {
+ log.trace(methodsParams[n] + " is not assignable from "
+ + parameterTypes[n]);
+ }
+ match = false;
+ break;
+ }
+ }
+
+ if (match) {
+ // get accessible version of method
+ Method method = getAccessibleMethod(clazz, methods[i]);
+ if (method != null) {
+ if (log.isTraceEnabled()) {
+ log.trace(method + " accessible version of "
+ + methods[i]);
+ }
+ setMethodAccessible(method); // Default access superclass workaround
+ myCost = getTotalTransformationCost(parameterTypes,method.getParameterTypes());
+ if ( myCost < bestMatchCost ) {
+ bestMatch = method;
+ bestMatchCost = myCost;
+ }
+ }
+
+ log.trace("Couldn't find accessible method.");
+ }
+ }
+ }
+ }
+ if ( bestMatch == null ){
+ // didn't find a match
+ log.trace("No match found.");
+ }
+
+ return bestMatch;
+ }
+
+ public static Constructor getMatchingAccessibleConstructor(
+ Class clazz,
+ Class[] parameterTypes) {
+ // trace logging
+ Log log = LogFactory.getLog(MethodUtils.class);
+ MethodDescriptor md = new MethodDescriptor(clazz, "dummy", parameterTypes, false);
+
+ // see if we can find the method directly
+ // most of the time this works and it's much faster
+ try {
+ Constructor constructor = clazz.getConstructor(parameterTypes);
+ if (log.isTraceEnabled()) {
+ log.trace("Found straight match: " + constructor);
+ log.trace("isPublic:" + Modifier.isPublic(constructor.getModifiers()));
+ }
+
+ setMethodAccessible(constructor); // Default access superclass workaround
+
+ return constructor;
+
+ } catch (NoSuchMethodException e) { /* SWALLOW */ }
+
+ // search through all methods
+ int paramSize = parameterTypes.length;
+ Constructor bestMatch = null;
+ Constructor>[] constructors = clazz.getConstructors();
+ float bestMatchCost = Float.MAX_VALUE;
+ float myCost = Float.MAX_VALUE;
+ for (int i = 0, size = constructors.length; i < size ; i++) {
+ // compare parameters
+ Class[] methodsParams = constructors[i].getParameterTypes();
+ int methodParamSize = methodsParams.length;
+ if (methodParamSize == paramSize) {
+ boolean match = true;
+ for (int n = 0 ; n < methodParamSize; n++) {
+ if (log.isTraceEnabled()) {
+ log.trace("Param=" + parameterTypes[n].getName());
+ log.trace("Method=" + methodsParams[n].getName());
+ }
+ if (!isAssignmentCompatible(methodsParams[n], parameterTypes[n])) {
+ if (log.isTraceEnabled()) {
+ log.trace(methodsParams[n] + " is not assignable from "
+ + parameterTypes[n]);
+ }
+ match = false;
+ break;
+ }
+ }
+
+ if (match) {
+ // get accessible version of method
+ Constructor cons = (Constructor)constructors[i];
+ myCost = getTotalTransformationCost(parameterTypes,cons.getParameterTypes());
+ if ( myCost < bestMatchCost ) {
+ bestMatch = cons;
+ bestMatchCost = myCost;
+ }
+ }
+ }
+ }
+ if ( bestMatch == null ){
+ // didn't find a match
+ log.trace("No match found.");
+ }
+
+ return bestMatch;
+ }
+
+ /**
+ * Try to make the method accessible
+ * @param method The source arguments
+ */
+ private static void setMethodAccessible(Object method) {
+ try {
+ //
+ // XXX Default access superclass workaround
+ //
+ // When a public class has a default access superclass
+ // with public methods, these methods are accessible.
+ // Calling them from compiled code works fine.
+ //
+ // Unfortunately, using reflection to invoke these methods
+ // seems to (wrongly) to prevent access even when the method
+ // modifer is public.
+ //
+ // The following workaround solves the problem but will only
+ // work from sufficiently privilages code.
+ //
+ // Better workarounds would be greatfully accepted.
+ //
+ if (method instanceof Method) {
+ ((Method)method).setAccessible(true);
+ } else if (method instanceof Constructor) {
+ ((Constructor)method).setAccessible(true);
+ } else {
+ throw new RuntimeException("invalid parameter");
+ }
+
+ } catch (SecurityException se) {
+ // log but continue just in case the method.invoke works anyway
+ Log log = LogFactory.getLog(MethodUtils.class);
+ if (!loggedAccessibleWarning) {
+ boolean vulnerableJVM = false;
+ try {
+ String specVersion = System.getProperty("java.specification.version");
+ if (specVersion.charAt(0) == '1' &&
+ (specVersion.charAt(2) == '0' ||
+ specVersion.charAt(2) == '1' ||
+ specVersion.charAt(2) == '2' ||
+ specVersion.charAt(2) == '3')) {
+
+ vulnerableJVM = true;
+ }
+ } catch (SecurityException e) {
+ // don't know - so display warning
+ vulnerableJVM = true;
+ }
+ if (vulnerableJVM) {
+ log.warn(
+ "Current Security Manager restricts use of workarounds for reflection bugs "
+ + " in pre-1.4 JVMs.");
+ }
+ loggedAccessibleWarning = true;
+ }
+ log.debug("Cannot setAccessible on method. Therefore cannot use jvm access bug workaround.", se);
+ }
+ }
+
+ /**
+ * Returns the sum of the object transformation cost for each class in the source
+ * argument list.
+ * @param srcArgs The source arguments
+ * @param destArgs The destination arguments
+ * @return The total transformation cost
+ */
+ private static float getTotalTransformationCost(Class[] srcArgs, Class[] destArgs) {
+
+ float totalCost = 0.0f;
+ for (int i = 0; i < srcArgs.length; i++) {
+ Class srcClass, destClass;
+ srcClass = srcArgs[i];
+ destClass = destArgs[i];
+ totalCost += getObjectTransformationCost(srcClass, destClass);
+ }
+
+ return totalCost;
+ }
+
+ /**
+ * Gets the number of steps required needed to turn the source class into the
+ * destination class. This represents the number of steps in the object hierarchy
+ * graph.
+ * @param srcClass The source class
+ * @param destClass The destination class
+ * @return The cost of transforming an object
+ */
+ private static float getObjectTransformationCost(Class srcClass, Class destClass) {
+ float cost = 0.0f;
+ while (destClass != null && !destClass.equals(srcClass)) {
+ if (destClass.isInterface() && isAssignmentCompatible(destClass,srcClass)) {
+ // slight penalty for interface match.
+ // we still want an exact match to override an interface match, but
+ // an interface match should override anything where we have to get a
+ // superclass.
+ cost += 0.25f;
+ break;
+ }
+ cost++;
+ destClass = destClass.getSuperclass();
+ }
+
+ /*
+ * If the destination class is null, we've travelled all the way up to
+ * an Object match. We'll penalize this by adding 1.5 to the cost.
+ */
+ if (destClass == null) {
+ cost += 1.5f;
+ }
+
+ return cost;
+ }
+
+
+ /**
+ * Determine whether a type can be used as a parameter in a method invocation.
+ * This method handles primitive conversions correctly.
+ *
+ * In order words, it will match a Boolean
to a boolean
,
+ * a Long
to a long
,
+ * a Float
to a float
,
+ * a Integer
to a int
,
+ * and a Double
to a double
.
+ * Now logic widening matches are allowed.
+ * For example, a Long
will not match a int
.
+ *
+ * @param parameterType the type of parameter accepted by the method
+ * @param parameterization the type of parameter being tested
+ *
+ * @return true if the assignement is compatible.
+ */
+ public static final boolean isAssignmentCompatible(Class parameterType, Class parameterization) {
+ // try plain assignment
+ if (parameterType.isAssignableFrom(parameterization)) {
+ return true;
+ }
+
+ if (parameterType.isPrimitive()) {
+ // this method does *not* do widening - you must specify exactly
+ // is this the right behaviour?
+ Class parameterWrapperClazz = getPrimitiveWrapper(parameterType);
+ if (parameterWrapperClazz != null) {
+ return parameterWrapperClazz.equals(parameterization);
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Gets the wrapper object class for the given primitive type class.
+ * For example, passing boolean.class
returns Boolean.class
+ * @param primitiveType the primitive type class for which a match is to be found
+ * @return the wrapper type associated with the given primitive
+ * or null if no match is found
+ */
+ public static Class getPrimitiveWrapper(Class primitiveType) {
+ // does anyone know a better strategy than comparing names?
+ if (boolean.class.equals(primitiveType)) {
+ return Boolean.class;
+ } else if (float.class.equals(primitiveType)) {
+ return Float.class;
+ } else if (long.class.equals(primitiveType)) {
+ return Long.class;
+ } else if (int.class.equals(primitiveType)) {
+ return Integer.class;
+ } else if (short.class.equals(primitiveType)) {
+ return Short.class;
+ } else if (byte.class.equals(primitiveType)) {
+ return Byte.class;
+ } else if (double.class.equals(primitiveType)) {
+ return Double.class;
+ } else if (char.class.equals(primitiveType)) {
+ return Character.class;
+ } else {
+
+ return null;
+ }
+ }
+
+ /**
+ * Gets the class for the primitive type corresponding to the primitive wrapper class given.
+ * For example, an instance of Boolean.class
returns a boolean.class
.
+ * @param wrapperType the
+ * @return the primitive type class corresponding to the given wrapper class,
+ * null if no match is found
+ */
+ public static Class getPrimitiveType(Class wrapperType) {
+ // does anyone know a better strategy than comparing names?
+ if (Boolean.class.equals(wrapperType)) {
+ return boolean.class;
+ } else if (Float.class.equals(wrapperType)) {
+ return float.class;
+ } else if (Long.class.equals(wrapperType)) {
+ return long.class;
+ } else if (Integer.class.equals(wrapperType)) {
+ return int.class;
+ } else if (Short.class.equals(wrapperType)) {
+ return short.class;
+ } else if (Byte.class.equals(wrapperType)) {
+ return byte.class;
+ } else if (Double.class.equals(wrapperType)) {
+ return double.class;
+ } else if (Character.class.equals(wrapperType)) {
+ return char.class;
+ } else {
+ Log log = LogFactory.getLog(MethodUtils.class);
+ if (log.isDebugEnabled()) {
+ log.debug("Not a known primitive wrapper class: " + wrapperType);
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Find a non primitive representation for given primitive class.
+ *
+ * @param clazz the class to find a representation for, not null
+ * @return the original class if it not a primitive. Otherwise the wrapper class. Not null
+ */
+ public static Class toNonPrimitiveClass(Class clazz) {
+ if (clazz.isPrimitive()) {
+ Class primitiveClazz = MethodUtils.getPrimitiveWrapper(clazz);
+ // the above method returns
+ if (primitiveClazz != null) {
+ return primitiveClazz;
+ } else {
+ return clazz;
+ }
+ } else {
+ return clazz;
+ }
+ }
+
+
+ /**
+ * Represents the key to looking up a Method by reflection.
+ */
+ private static class MethodDescriptor {
+ private Class cls;
+ private String methodName;
+ private Class[] paramTypes;
+ private boolean exact;
+ private int hashCode;
+
+ /**
+ * The sole constructor.
+ *
+ * @param cls the class to reflect, must not be null
+ * @param methodName the method name to obtain
+ * @param paramTypes the array of classes representing the paramater types
+ * @param exact whether the match has to be exact.
+ */
+ public MethodDescriptor(Class cls, String methodName, Class[] paramTypes, boolean exact) {
+ if (cls == null) {
+ throw new IllegalArgumentException("Class cannot be null");
+ }
+ if (methodName == null) {
+ throw new IllegalArgumentException("Method Name cannot be null");
+ }
+ if (paramTypes == null) {
+ paramTypes = EMPTY_CLASS_PARAMETERS;
+ }
+
+ this.cls = cls;
+ this.methodName = methodName;
+ this.paramTypes = paramTypes;
+ this.exact= exact;
+
+ this.hashCode = methodName.length();
+ }
+ /**
+ * Checks for equality.
+ * @param obj object to be tested for equality
+ * @return true, if the object describes the same Method.
+ */
+ public boolean equals(Object obj) {
+ if (!(obj instanceof MethodDescriptor)) {
+ return false;
+ }
+ MethodDescriptor md = (MethodDescriptor)obj;
+
+ return (
+ exact == md.exact &&
+ methodName.equals(md.methodName) &&
+ cls.equals(md.cls) &&
+ java.util.Arrays.equals(paramTypes, md.paramTypes)
+ );
+ }
+ /**
+ * Returns the string length of method name. I.e. if the
+ * hashcodes are different, the objects are different. If the
+ * hashcodes are the same, need to use the equals method to
+ * determine equality.
+ * @return the string length of method name.
+ */
+ public int hashCode() {
+ return hashCode;
+ }
+ }
+}
diff --git a/src/ooxml/java/org/apache/poi/util/SAXHelper.java b/src/ooxml/java/org/apache/poi/util/SAXHelper.java
index b38b2c2be..9ee00fb69 100644
--- a/src/ooxml/java/org/apache/poi/util/SAXHelper.java
+++ b/src/ooxml/java/org/apache/poi/util/SAXHelper.java
@@ -23,6 +23,9 @@ import java.io.StringReader;
import java.lang.reflect.Method;
import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
import org.dom4j.Document;
import org.dom4j.DocumentException;
@@ -89,4 +92,72 @@ public final class SAXHelper {
public static Document readSAXDocument(InputStream inp) throws DocumentException {
return getSAXReader().read(inp);
}
+
+ private static final EntityResolver IGNORING_ENTITY_RESOLVER = new EntityResolver() {
+ @Override
+ public InputSource resolveEntity(String publicId, String systemId)
+ throws SAXException, IOException {
+ return new InputSource(new StringReader(""));
+ }
+ };
+
+ private static void trySetSAXFeature(DocumentBuilderFactory documentBuilderFactory, String feature, boolean enabled) {
+ try {
+ documentBuilderFactory.setFeature(feature, enabled);
+ } catch (Exception e) {
+ logger.log(POILogger.INFO, "SAX Feature unsupported", feature, e);
+ }
+ }
+ private static void trySetXercesSecurityManager(DocumentBuilderFactory documentBuilderFactory) {
+ // Try built-in JVM one first, standalone if not
+ for (String securityManagerClassName : new String[] {
+ "com.sun.org.apache.xerces.internal.util.SecurityManager",
+ "org.apache.xerces.util.SecurityManager"
+ }) {
+ try {
+ Object mgr = Class.forName(securityManagerClassName).newInstance();
+ Method setLimit = mgr.getClass().getMethod("setEntityExpansionLimit", Integer.TYPE);
+ setLimit.invoke(mgr, 4096);
+ documentBuilderFactory.setAttribute("http://apache.org/xml/properties/security-manager", mgr);
+ // Stop once one can be setup without error
+ return;
+ } catch (Exception e) {
+ logger.log(POILogger.INFO, "SAX Security Manager could not be setup", e);
+ }
+ }
+ }
+
+ private static final ThreadLocal documentBuilder = new ThreadLocal() {
+ @Override
+ protected DocumentBuilder initialValue() {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true);
+ factory.setValidating(false);
+ trySetSAXFeature(factory, XMLConstants.FEATURE_SECURE_PROCESSING, true);
+ trySetXercesSecurityManager(factory);
+ try {
+ return factory.newDocumentBuilder();
+ } catch (ParserConfigurationException e) {
+ throw new IllegalStateException("cannot create a DocumentBuilder", e);
+ }
+ }
+
+ @Override
+ public DocumentBuilder get() {
+ DocumentBuilder documentBuilder = super.get();
+ documentBuilder.reset();
+ documentBuilder.setEntityResolver(IGNORING_ENTITY_RESOLVER);
+ return documentBuilder;
+ }
+ };
+
+ /**
+ * Parses the given stream via the default (sensible)
+ * SAX Reader
+ * @param inp Stream to read the XML data from
+ * @return the SAX processed Document
+ */
+ public static org.w3c.dom.Document readSAXDocumentW3C(InputStream inp) throws IOException, SAXException {
+ return documentBuilder.get().parse(inp);
+ }
}
diff --git a/src/ooxml/java/org/apache/poi/util/XmlSort.java b/src/ooxml/java/org/apache/poi/util/XmlSort.java
new file mode 100644
index 000000000..4e1ffa54f
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/util/XmlSort.java
@@ -0,0 +1,221 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Comparator;
+
+import javax.xml.namespace.QName;
+
+import org.apache.xmlbeans.XmlCursor;
+import org.apache.xmlbeans.XmlException;
+import org.apache.xmlbeans.XmlObject;
+
+/**
+ */
+public final class XmlSort
+{
+ /**
+ * Receives an XML element instance and sorts the children of this
+ * element in lexicographical (by default) order.
+ *
+ * @param args An array in which the first item is a
+ * path to the XML instance file and the second item (optional) is
+ * an XPath inside the document identifying the element to be sorted
+ */
+ public static void main(String[] args)
+ {
+ if (args.length < 1 || args.length > 2)
+ {
+ System.out.println(" java XmlSort []");
+ return;
+ }
+ File f = new File(args[0]);
+ try
+ {
+ XmlObject docInstance = XmlObject.Factory.parse(f);
+ XmlObject element = null;
+ if (args.length > 1)
+ {
+ String xpath = args[1];
+ XmlObject[] result = docInstance.selectPath(xpath);
+ if (result.length == 0)
+ {
+ System.out.println("ERROR: XPath \"" + xpath + "\" did not return any results");
+ }
+ else if (result.length > 1)
+ {
+ System.out.println("ERROR: XPath \"" + xpath + "\" returned more than one " +
+ "node (" + result.length + ")");
+ }
+ else
+ element = result[0];
+ }
+ else
+ {
+ // Navigate to the root element
+ XmlCursor c = docInstance.newCursor();
+ c.toFirstChild();
+ element = c.getObject();
+ c.dispose();
+ }
+ if (element != null)
+ sort(element, new QNameComparator(QNameComparator.ASCENDING));
+ System.out.println(docInstance.xmlText());
+ }
+ catch (IOException ioe)
+ {
+ System.out.println("ERROR: Could not open file: \"" + args[0] + "\": " +
+ ioe.getMessage());
+ }
+ catch (XmlException xe)
+ {
+ System.out.println("ERROR: Could not parse file: \"" + args[0] + "\": " +
+ xe.getMessage());
+ }
+ }
+
+ /**
+ * Sorts the children of element
according to the order indicated by the
+ * comparator.
+ * @param element the element whose content is to be sorted. Only element children are sorted,
+ * attributes are not touched. When elements are reordered, all the text, comments and PIs
+ * follow the element that they come immediately after.
+ * @param comp a comparator that is to be used when comparing the QName
s of two
+ * elements. See {@link org.apache.xmlbeans.samples.cursor.XmlSort.QNameComparator} for a simple
+ * implementation that compares two elements based on the value of their QName, but more
+ * complicated implementations are possible, for instance, ones that compare two elements based
+ * on the value of a specifc attribute etc.
+ * @throws IllegalArgumentException if the input XmlObject
does not represent
+ * an element
+ */
+ public static void sort(XmlObject element, Comparator comp)
+ {
+ XmlCursor headCursor = element.newCursor();
+ if (!headCursor.isStart())
+ throw new IllegalStateException("The element parameter must point to a STARTDOC");
+ // We use insertion sort to minimize the number of swaps, because each swap means
+ // moving a part of the document
+ /* headCursor points to the beginning of the list of the already sorted items and
+ listCursor points to the beginning of the list of unsorted items
+ At the beginning, headCursor points to the first element and listCursor points to the
+ second element. The algorithm ends when listCursor cannot be moved to the "next"
+ element in the unsorted list, i.e. the unsorted list becomes empty */
+ boolean moved = headCursor.toFirstChild();
+ if (!moved)
+ {
+ // Cursor was not moved, which means that the given element has no children and
+ // therefore there is nothing to sort
+ return;
+ }
+ XmlCursor listCursor = headCursor.newCursor();
+ boolean moreElements = listCursor.toNextSibling();
+ while (moreElements)
+ {
+ moved = false;
+ // While we can move the head of the unsorted list, it means that there are still
+ // items (elements) that need to be sorted
+ while (headCursor.comparePosition(listCursor) < 0)
+ {
+ if (comp.compare(headCursor, listCursor) > 0)
+ {
+ // We have found the position in the sorted list, insert the element and the
+ // text following the element in the current position
+ /*
+ * Uncomment this code to cause the text before the element to move along
+ * with the element, rather than the text after the element. Notice that this
+ * is more difficult to do, because the cursor's "type" refers to the position
+ * to the right of the cursor, so to get the type of the token to the left, the
+ * cursor needs to be first moved to the left (previous token)
+ *
+ headCursor.toPrevToken();
+ while (headCursor.isComment() || headCursor.isProcinst() || headCursor.isText())
+ headCursor.toPrevToken();
+ headCursor.toNextToken();
+ listCursor.toPrevToken();
+ while (listCursor.isComment() || listCursor.isProcinst() || listCursor.isText())
+ listCursor.toPrevToken();
+ listCursor.toNextToken();
+ while (!listCursor.isStart())
+ listCursor.moveXml(headCursor);
+ listCursor.moveXml(headCursor);
+ */
+ // Move the element
+ listCursor.moveXml(headCursor);
+ // Move the text following the element
+ while (!listCursor.isStart() && !listCursor.isEnd())
+ listCursor.moveXml(headCursor);
+ moreElements = listCursor.isStart();
+ moved = true;
+ break;
+ }
+ headCursor.toNextSibling();
+ }
+ if (!moved)
+ {
+ // Because during the move of a fragment of XML, the listCursor is also moved, in
+ // case we didn't need to move XML (the new element to be inserted happened to
+ // be the last one in order), we need to move this cursor
+ moreElements = listCursor.toNextSibling();
+ }
+ // Reposition the head of the sorted list
+ headCursor.toParent();
+ headCursor.toFirstChild();
+ }
+ }
+
+ /**
+ * Implements a java.util.Comparator
for comparing QName
values.
+ * The namespace URIs are compared first and if they are equal, the local parts are compared.
+ *
+ * The constructor accepts an argument indicating whether the comparison order is the same as
+ * the lexicographic order of the strings or the reverse.
+ */
+ public static final class QNameComparator implements Comparator
+ {
+ public static final int ASCENDING = 1;
+ public static final int DESCENDING = 2;
+
+ private int order;
+
+ public QNameComparator(int order)
+ {
+ this.order = order;
+ if (order != ASCENDING && order != DESCENDING)
+ throw new IllegalArgumentException("Please specify one of ASCENDING or DESCENDING "+
+ "comparison orders");
+ }
+
+ public int compare(Object o, Object o1)
+ {
+ XmlCursor cursor1 = (XmlCursor) o;
+ XmlCursor cursor2 = (XmlCursor) o1;
+ QName qname1 = cursor1.getName();
+ QName qname2 = cursor2.getName();
+ int qnameComparisonRes = qname1.getNamespaceURI().compareTo(qname2.getNamespaceURI());
+ if (qnameComparisonRes == 0)
+ return order == ASCENDING ?
+ qname1.getLocalPart().compareTo(qname2.getLocalPart()) :
+ -qname1.getLocalPart().compareTo(qname2.getLocalPart());
+ else
+ return order == ASCENDING ? qnameComparisonRes : -qnameComparisonRes;
+ }
+ }
+}
+
\ No newline at end of file
diff --git a/src/ooxml/resources/org/apache/poi/poifs/crypt/XAdES.xsd b/src/ooxml/resources/org/apache/poi/poifs/crypt/XAdES.xsd
new file mode 100644
index 000000000..656e721c7
--- /dev/null
+++ b/src/ooxml/resources/org/apache/poi/poifs/crypt/XAdES.xsd
@@ -0,0 +1,466 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ooxml/resources/org/apache/poi/poifs/crypt/XAdESv141.xsd b/src/ooxml/resources/org/apache/poi/poifs/crypt/XAdESv141.xsd
new file mode 100644
index 000000000..cd6614f13
--- /dev/null
+++ b/src/ooxml/resources/org/apache/poi/poifs/crypt/XAdESv141.xsd
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ooxml/resources/org/apache/poi/poifs/crypt/signatureInfo.xsd b/src/ooxml/resources/org/apache/poi/poifs/crypt/signatureInfo.xsd
new file mode 100644
index 000000000..f7019f13f
--- /dev/null
+++ b/src/ooxml/resources/org/apache/poi/poifs/crypt/signatureInfo.xsd
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ooxml/testcases/org/apache/poi/poifs/crypt/PkiTestUtils.java b/src/ooxml/testcases/org/apache/poi/poifs/crypt/PkiTestUtils.java
new file mode 100644
index 000000000..6d6592a2a
--- /dev/null
+++ b/src/ooxml/testcases/org/apache/poi/poifs/crypt/PkiTestUtils.java
@@ -0,0 +1,328 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.poifs.crypt;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.lang.reflect.InvocationTargetException;
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.SignatureException;
+import java.security.cert.CRLException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509CRL;
+import java.security.cert.X509Certificate;
+import java.security.spec.RSAKeyGenParameterSpec;
+import java.util.Date;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.apache.poi.poifs.crypt.dsig.HorribleProxy;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.ASN1InputStreamIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.AuthorityInformationAccessIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.AuthorityKeyIdentifierIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.BasicConstraintsIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.BasicOCSPRespGeneratorIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.BasicOCSPRespIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.CRLNumberIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.CRLReasonIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.CertificateIDIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.CertificateStatusIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.DERIA5StringIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.DERSequenceIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.DistributionPointIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.DistributionPointNameIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.GeneralNameIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.GeneralNamesIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.KeyUsageIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.OCSPReqGeneratorIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.OCSPReqIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.OCSPRespGeneratorIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.OCSPRespIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.ReqIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.RevokedStatusIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.SubjectKeyIdentifierIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.SubjectPublicKeyInfoIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.X509ExtensionsIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.X509ObjectIdentifiersIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.X509PrincipalIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.X509V2CRLGeneratorIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.X509V3CertificateGeneratorIf;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+public class PkiTestUtils {
+
+ private PkiTestUtils() {
+ super();
+ }
+
+ static KeyPair generateKeyPair() throws Exception {
+ KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
+ SecureRandom random = new SecureRandom();
+ keyPairGenerator.initialize(new RSAKeyGenParameterSpec(1024,
+ RSAKeyGenParameterSpec.F4), random);
+ KeyPair keyPair = keyPairGenerator.generateKeyPair();
+ return keyPair;
+ }
+
+ private static SubjectKeyIdentifierIf createSubjectKeyId(PublicKey publicKey)
+ throws IOException, ClassNotFoundException, NoSuchMethodException, InstantiationException
+ , IllegalAccessException, InvocationTargetException, NoSuchFieldException {
+ ByteArrayInputStream bais = new ByteArrayInputStream(publicKey.getEncoded());
+ ASN1InputStreamIf asnObj = HorribleProxy.newProxy(ASN1InputStreamIf.class, bais);
+ SubjectPublicKeyInfoIf info =
+ HorribleProxy.newProxy(SubjectPublicKeyInfoIf.class, asnObj.readObject$Sequence());
+ SubjectKeyIdentifierIf keyId = HorribleProxy.newProxy(SubjectKeyIdentifierIf.class, info);
+ return keyId;
+ }
+
+ private static AuthorityKeyIdentifierIf createAuthorityKeyId(PublicKey publicKey)
+ throws IOException, ClassNotFoundException, NoSuchMethodException, InstantiationException
+ , IllegalAccessException, InvocationTargetException, NoSuchFieldException {
+
+ ByteArrayInputStream bais = new ByteArrayInputStream(publicKey.getEncoded());
+ ASN1InputStreamIf asnObj = HorribleProxy.newProxy(ASN1InputStreamIf.class, bais);
+ SubjectPublicKeyInfoIf info =
+ HorribleProxy.newProxy(SubjectPublicKeyInfoIf.class, asnObj.readObject$Sequence());
+ AuthorityKeyIdentifierIf keyId = HorribleProxy.newProxy(AuthorityKeyIdentifierIf.class, info);
+
+ return keyId;
+ }
+
+ static X509Certificate generateCertificate(PublicKey subjectPublicKey,
+ String subjectDn, Date notBefore, Date notAfter,
+ X509Certificate issuerCertificate, PrivateKey issuerPrivateKey,
+ boolean caFlag, int pathLength, String crlUri, String ocspUri,
+ KeyUsageIf keyUsage)
+ throws IOException, InvalidKeyException, IllegalStateException, NoSuchAlgorithmException
+ , SignatureException, CertificateException, InvocationTargetException, IllegalAccessException
+ , InstantiationException, NoSuchMethodException, ClassNotFoundException, NoSuchFieldException
+ {
+ String signatureAlgorithm = "SHA1withRSA";
+ X509V3CertificateGeneratorIf certificateGenerator = HorribleProxy.newProxy(X509V3CertificateGeneratorIf.class);
+ certificateGenerator.reset();
+ certificateGenerator.setPublicKey(subjectPublicKey);
+ certificateGenerator.setSignatureAlgorithm(signatureAlgorithm);
+ certificateGenerator.setNotBefore(notBefore);
+ certificateGenerator.setNotAfter(notAfter);
+ X509PrincipalIf subjectDN = HorribleProxy.newProxy(X509PrincipalIf.class, subjectDn);
+ X509PrincipalIf issuerDN;
+ if (null != issuerCertificate) {
+ issuerDN = HorribleProxy.newProxy(X509PrincipalIf.class, issuerCertificate
+ .getSubjectX500Principal().toString());
+ } else {
+ issuerDN = subjectDN;
+ }
+ certificateGenerator.setIssuerDN(issuerDN);
+ certificateGenerator.setSubjectDN(subjectDN);
+ certificateGenerator.setSerialNumber(new BigInteger(128,
+ new SecureRandom()));
+
+ X509ExtensionsIf X509Extensions = HorribleProxy.newProxy(X509ExtensionsIf.class);
+
+ certificateGenerator.addExtension(X509Extensions.SubjectKeyIdentifier(),
+ false, createSubjectKeyId(subjectPublicKey));
+ PublicKey issuerPublicKey;
+ issuerPublicKey = subjectPublicKey;
+ certificateGenerator.addExtension(
+ X509Extensions.AuthorityKeyIdentifier(), false,
+ createAuthorityKeyId(issuerPublicKey));
+
+ if (caFlag) {
+ BasicConstraintsIf bc;
+
+ if (-1 == pathLength) {
+ bc = HorribleProxy.newProxy(BasicConstraintsIf.class, true);
+ } else {
+ bc = HorribleProxy.newProxy(BasicConstraintsIf.class, pathLength);
+ }
+ certificateGenerator.addExtension(X509Extensions.BasicConstraints(), false, bc);
+ }
+
+ if (null != crlUri) {
+ GeneralNameIf gn = HorribleProxy.newProxy(GeneralNameIf.class);
+ int uri = gn.uniformResourceIdentifier();
+ DERIA5StringIf crlUriDer = HorribleProxy.newProxy(DERIA5StringIf.class, crlUri);
+ gn = HorribleProxy.newProxy(GeneralNameIf.class, uri, crlUriDer);
+
+ DERSequenceIf gnDer = HorribleProxy.newProxy(DERSequenceIf.class, gn);
+ GeneralNamesIf gns = HorribleProxy.newProxy(GeneralNamesIf.class, gnDer);
+
+ DistributionPointNameIf dpn = HorribleProxy.newProxy(DistributionPointNameIf.class, 0, gns);
+ DistributionPointIf distp = HorribleProxy.newProxy(DistributionPointIf.class, dpn, null, null);
+ DERSequenceIf distpDer = HorribleProxy.newProxy(DERSequenceIf.class, distp);
+ certificateGenerator.addExtension(X509Extensions.CRLDistributionPoints(), false, distpDer);
+ }
+
+ if (null != ocspUri) {
+ GeneralNameIf ocspName = HorribleProxy.newProxy(GeneralNameIf.class);
+ int uri = ocspName.uniformResourceIdentifier();
+ ocspName = HorribleProxy.newProxy(GeneralNameIf.class, uri, ocspUri);
+
+ X509ObjectIdentifiersIf X509ObjectIdentifiers = HorribleProxy.newProxy(X509ObjectIdentifiersIf.class);
+ AuthorityInformationAccessIf authorityInformationAccess =
+ HorribleProxy.newProxy(AuthorityInformationAccessIf.class
+ , X509ObjectIdentifiers.ocspAccessMethod(), ocspName);
+
+ certificateGenerator.addExtension(
+ X509Extensions.AuthorityInfoAccess(), false,
+ authorityInformationAccess);
+ }
+
+ if (null != keyUsage) {
+ certificateGenerator.addExtension(X509Extensions.KeyUsage(), true, keyUsage);
+ }
+
+ X509Certificate certificate;
+ certificate = certificateGenerator.generate(issuerPrivateKey);
+
+ /*
+ * Next certificate factory trick is needed to make sure that the
+ * certificate delivered to the caller is provided by the default
+ * security provider instead of BouncyCastle. If we don't do this trick
+ * we might run into trouble when trying to use the CertPath validator.
+ */
+ CertificateFactory certificateFactory = CertificateFactory
+ .getInstance("X.509");
+ certificate = (X509Certificate) certificateFactory
+ .generateCertificate(new ByteArrayInputStream(certificate
+ .getEncoded()));
+ return certificate;
+ }
+
+ static Document loadDocument(InputStream documentInputStream)
+ throws ParserConfigurationException, SAXException, IOException {
+ InputSource inputSource = new InputSource(documentInputStream);
+ DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory
+ .newInstance();
+ documentBuilderFactory.setNamespaceAware(true);
+ DocumentBuilder documentBuilder = documentBuilderFactory
+ .newDocumentBuilder();
+ Document document = documentBuilder.parse(inputSource);
+ return document;
+ }
+
+ static String toString(Node dom) throws TransformerException {
+ Source source = new DOMSource(dom);
+ StringWriter stringWriter = new StringWriter();
+ Result result = new StreamResult(stringWriter);
+ TransformerFactory transformerFactory = TransformerFactory
+ .newInstance();
+ Transformer transformer = transformerFactory.newTransformer();
+ /*
+ * We have to omit the ?xml declaration if we want to embed the
+ * document.
+ */
+ transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
+ transformer.transform(source, result);
+ return stringWriter.getBuffer().toString();
+ }
+
+ public static X509CRL generateCrl(X509Certificate issuer,
+ PrivateKey issuerPrivateKey) throws InvalidKeyException,
+ CRLException, IllegalStateException, NoSuchAlgorithmException,
+ SignatureException, InvocationTargetException, IllegalAccessException,
+ InstantiationException, NoSuchMethodException, ClassNotFoundException, NoSuchFieldException {
+ X509V2CRLGeneratorIf crlGenerator = HorribleProxy.newProxy(X509V2CRLGeneratorIf.class);
+ crlGenerator.setIssuerDN(issuer.getSubjectX500Principal());
+ Date now = new Date();
+ crlGenerator.setThisUpdate(now);
+ crlGenerator.setNextUpdate(new Date(now.getTime() + 100000));
+ crlGenerator.setSignatureAlgorithm("SHA1withRSA");
+
+ X509ExtensionsIf X509Extensions = HorribleProxy.newProxy(X509ExtensionsIf.class);
+ CRLNumberIf crlNumber = HorribleProxy.newProxy(CRLNumberIf.class, new BigInteger("1234"));
+
+ crlGenerator.addExtension(X509Extensions.CRLNumber(), false, crlNumber);
+ X509CRL x509Crl = crlGenerator.generate(issuerPrivateKey);
+ return x509Crl;
+ }
+
+ public static OCSPRespIf createOcspResp(X509Certificate certificate,
+ boolean revoked, X509Certificate issuerCertificate,
+ X509Certificate ocspResponderCertificate,
+ PrivateKey ocspResponderPrivateKey, String signatureAlgorithm)
+ throws Exception {
+ // request
+ OCSPReqGeneratorIf ocspReqGenerator = HorribleProxy.newProxy(OCSPReqGeneratorIf.class);
+ CertificateIDIf certId = HorribleProxy.newProxy(CertificateIDIf.class);
+ certId = HorribleProxy.newProxy(CertificateIDIf.class, certId.HASH_SHA1(),
+ issuerCertificate, certificate.getSerialNumber());
+ ocspReqGenerator.addRequest(certId);
+ OCSPReqIf ocspReq = ocspReqGenerator.generate();
+
+ BasicOCSPRespGeneratorIf basicOCSPRespGenerator =
+ HorribleProxy.newProxy(BasicOCSPRespGeneratorIf.class, ocspResponderCertificate.getPublicKey());
+
+ // request processing
+ ReqIf[] requestList = ocspReq.getRequestList();
+ for (ReqIf ocspRequest : requestList) {
+ CertificateIDIf certificateID = ocspRequest.getCertID();
+ CertificateStatusIf certificateStatus;
+ if (revoked) {
+ CRLReasonIf crlr = HorribleProxy.newProxy(CRLReasonIf.class);
+ RevokedStatusIf rs = HorribleProxy.newProxy(RevokedStatusIf.class, new Date(), crlr.unspecified());
+ certificateStatus = HorribleProxy.newProxy(CertificateStatusIf.class, rs.getDelegate());
+ } else {
+ CertificateStatusIf cs = HorribleProxy.newProxy(CertificateStatusIf.class);
+ certificateStatus = cs.GOOD();
+ }
+ basicOCSPRespGenerator
+ .addResponse(certificateID, certificateStatus);
+ }
+
+ // basic response generation
+ X509Certificate[] chain = null;
+ if (!ocspResponderCertificate.equals(issuerCertificate)) {
+ chain = new X509Certificate[] { ocspResponderCertificate,
+ issuerCertificate };
+ }
+
+ BasicOCSPRespIf basicOCSPResp = basicOCSPRespGenerator.generate(
+ signatureAlgorithm, ocspResponderPrivateKey, chain, new Date(),
+ "BC");
+
+ // response generation
+ OCSPRespGeneratorIf ocspRespGenerator = HorribleProxy.newProxy(OCSPRespGeneratorIf.class);
+ OCSPRespIf ocspResp = ocspRespGenerator.generate(
+ ocspRespGenerator.SUCCESSFUL(), basicOCSPResp);
+
+ return ocspResp;
+ }
+}
diff --git a/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java b/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java
new file mode 100644
index 000000000..f155620e2
--- /dev/null
+++ b/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java
@@ -0,0 +1,266 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+/* ====================================================================
+ This product contains an ASLv2 licensed version of the OOXML signer
+ package from the eID Applet project
+ http://code.google.com/p/eid-applet/source/browse/trunk/README.txt
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+package org.apache.poi.poifs.crypt;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.Key;
+import java.security.KeyPair;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.TimeZone;
+
+import javax.crypto.Cipher;
+
+import org.apache.poi.POIDataSamples;
+import org.apache.poi.openxml4j.opc.OPCPackage;
+import org.apache.poi.openxml4j.opc.PackageAccess;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxy;
+import org.apache.poi.poifs.crypt.dsig.SignatureInfo;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.KeyUsageIf;
+import org.apache.poi.poifs.crypt.dsig.services.XmlSignatureService;
+import org.apache.poi.poifs.crypt.dsig.spi.DigestInfo;
+import org.apache.poi.util.IOUtils;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class TestSignatureInfo {
+ private static final POILogger LOG = POILogFactory.getLogger(TestSignatureInfo.class);
+ private static final POIDataSamples testdata = POIDataSamples.getXmlDSignInstance();
+
+ private KeyPair keyPair = null;
+ private X509Certificate x509 = null;
+
+
+
+ @BeforeClass
+ public static void initBouncy() throws MalformedURLException {
+ File bcJar = testdata.getFile("bcprov-ext-jdk15on-1.49.jar");
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ URLClassLoader ucl = new URLClassLoader(new URL[]{bcJar.toURI().toURL()}, cl);
+ Thread.currentThread().setContextClassLoader(ucl);
+ }
+
+ @Test
+ public void getSignerUnsigned() throws Exception {
+ String testFiles[] = {
+ "hello-world-unsigned.docx",
+ "hello-world-unsigned.pptx",
+ "hello-world-unsigned.xlsx",
+ "hello-world-office-2010-technical-preview-unsigned.docx"
+ };
+
+ for (String testFile : testFiles) {
+ OPCPackage pkg = OPCPackage.open(testdata.getFile(testFile), PackageAccess.READ);
+ SignatureInfo si = new SignatureInfo(pkg);
+ List result = si.getSigners();
+ pkg.revert();
+ pkg.close();
+ assertNotNull(result);
+ assertTrue(result.isEmpty());
+ }
+ }
+
+ @Test
+ public void getSigner() throws Exception {
+ String testFiles[] = {
+ "hyperlink-example-signed.docx",
+ "hello-world-signed.docx",
+ "hello-world-signed.pptx",
+ "hello-world-signed.xlsx",
+ "hello-world-office-2010-technical-preview.docx",
+ "ms-office-2010-signed.docx",
+ "ms-office-2010-signed.pptx",
+ "ms-office-2010-signed.xlsx",
+ "Office2010-SP1-XAdES-X-L.docx",
+ "signed.docx",
+ };
+
+ for (String testFile : testFiles) {
+ OPCPackage pkg = OPCPackage.open(testdata.getFile(testFile), PackageAccess.READ);
+ SignatureInfo si = new SignatureInfo(pkg);
+ List result = si.getSigners();
+
+ assertNotNull(result);
+ assertEquals("test-file: "+testFile, 1, result.size());
+ X509Certificate signer = result.get(0);
+ LOG.log(POILogger.DEBUG, "signer: " + signer.getSubjectX500Principal());
+
+ boolean b = si.verifySignature();
+ assertTrue("test-file: "+testFile, b);
+ pkg.revert();
+ }
+ }
+
+ @Test
+ public void getMultiSigners() throws Exception {
+ String testFile = "hello-world-signed-twice.docx";
+ OPCPackage pkg = OPCPackage.open(testdata.getFile(testFile), PackageAccess.READ);
+ SignatureInfo si = new SignatureInfo(pkg);
+ List result = si.getSigners();
+
+ assertNotNull(result);
+ assertEquals("test-file: "+testFile, 2, result.size());
+ X509Certificate signer1 = result.get(0);
+ X509Certificate signer2 = result.get(1);
+ LOG.log(POILogger.DEBUG, "signer 1: " + signer1.getSubjectX500Principal());
+ LOG.log(POILogger.DEBUG, "signer 2: " + signer2.getSubjectX500Principal());
+
+ boolean b = si.verifySignature();
+ assertTrue("test-file: "+testFile, b);
+ pkg.revert();
+ }
+
+ @Test
+ public void testSignSpreadsheet() throws Exception {
+ String testFile = "hello-world-unsigned.xlsx";
+ OPCPackage pkg = OPCPackage.open(copy(testdata.getFile(testFile)), PackageAccess.READ_WRITE);
+ sign(pkg, "Test", "CN=Test", 1);
+ pkg.close();
+ }
+
+ @Test
+ public void testSignSpreadsheetWithSignatureInfo() throws Exception {
+ String testFile = "hello-world-unsigned.xlsx";
+ OPCPackage pkg = OPCPackage.open(copy(testdata.getFile(testFile)), PackageAccess.READ_WRITE);
+ SignatureInfo si = new SignatureInfo(pkg);
+ initKeyPair("Test", "CN=Test");
+ si.confirmSignature(keyPair.getPrivate(), x509, HashAlgorithm.sha1);
+ List signer = si.getSigners();
+ assertEquals(1, signer.size());
+ pkg.close();
+ }
+
+
+ private OPCPackage sign(OPCPackage pkgCopy, String alias, String signerDn, int signerCount) throws Exception {
+ /*** TODO : set cal to now ... only set to fixed date for debugging ... */
+ Calendar cal = Calendar.getInstance();
+ cal.clear();
+ cal.setTimeZone(TimeZone.getTimeZone("UTC"));
+ cal.set(2014, 7, 6, 21, 42, 12);
+
+ XmlSignatureService signatureService = new XmlSignatureService(HashAlgorithm.sha1, pkgCopy);
+ signatureService.initFacets(cal.getTime());
+ initKeyPair(alias, signerDn);
+
+ // operate
+ List x509Chain = Collections.singletonList(x509);
+ DigestInfo digestInfo = signatureService.preSign(null, x509Chain, null, null, null);
+
+ // verify
+ assertNotNull(digestInfo);
+ LOG.log(POILogger.DEBUG, "digest algo: " + digestInfo.hashAlgo);
+ LOG.log(POILogger.DEBUG, "digest description: " + digestInfo.description);
+ assertEquals("Office OpenXML Document", digestInfo.description);
+ assertNotNull(digestInfo.hashAlgo);
+ assertNotNull(digestInfo.digestValue);
+
+ // setup: key material, signature value
+
+ Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
+ cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPrivate());
+ ByteArrayOutputStream digestInfoValueBuf = new ByteArrayOutputStream();
+ digestInfoValueBuf.write(SignatureInfo.SHA1_DIGEST_INFO_PREFIX);
+ digestInfoValueBuf.write(digestInfo.digestValue);
+ byte[] digestInfoValue = digestInfoValueBuf.toByteArray();
+ byte[] signatureValue = cipher.doFinal(digestInfoValue);
+
+ // operate: postSign
+ signatureService.postSign(signatureValue, Collections.singletonList(x509));
+
+ // verify: signature
+ SignatureInfo si = new SignatureInfo(pkgCopy);
+ List signers = si.getSigners();
+ assertEquals(signerCount, signers.size());
+
+ return pkgCopy;
+ }
+
+ private void initKeyPair(String alias, String subjectDN) throws Exception {
+ final char password[] = "test".toCharArray();
+ File file = new File("build/test.pfx");
+
+ KeyStore keystore = KeyStore.getInstance("PKCS12");
+
+ if (file.exists()) {
+ FileInputStream fis = new FileInputStream(file);
+ keystore.load(fis, password);
+ fis.close();
+ } else {
+ keystore.load(null, password);
+ }
+
+ if (keystore.isKeyEntry(alias)) {
+ Key key = keystore.getKey(alias, password);
+ x509 = (X509Certificate)keystore.getCertificate(alias);
+ keyPair = new KeyPair(x509.getPublicKey(), (PrivateKey)key);
+ } else {
+ keyPair = PkiTestUtils.generateKeyPair();
+ Calendar cal = Calendar.getInstance();
+ Date notBefore = cal.getTime();
+ cal.add(Calendar.YEAR, 1);
+ Date notAfter = cal.getTime();
+ KeyUsageIf keyUsage = HorribleProxy.newProxy(KeyUsageIf.class);
+ keyUsage = HorribleProxy.newProxy(KeyUsageIf.class, keyUsage.digitalSignature());
+
+ x509 = PkiTestUtils.generateCertificate(keyPair.getPublic(), subjectDN
+ , notBefore, notAfter, null, keyPair.getPrivate(), true, 0, null, null, keyUsage);
+
+ keystore.setKeyEntry(alias, keyPair.getPrivate(), password, new Certificate[]{x509});
+ FileOutputStream fos = new FileOutputStream(file);
+ keystore.store(fos, password);
+ fos.close();
+ }
+ }
+
+ private static File copy(File input) throws IOException {
+ String extension = input.getName().replaceAll(".*?(\\.[^.]+)?$", "$1");
+ if (extension == null || "".equals(extension)) extension = ".zip";
+ File tmpFile = new File("build", "sigtest"+extension);
+ FileOutputStream fos = new FileOutputStream(tmpFile);
+ FileInputStream fis = new FileInputStream(input);
+ IOUtils.copy(fis, fos);
+ fis.close();
+ fos.close();
+ return tmpFile;
+ }
+}
diff --git a/src/testcases/org/apache/poi/POIDataSamples.java b/src/testcases/org/apache/poi/POIDataSamples.java
index b33521539..a62e664e1 100644
--- a/src/testcases/org/apache/poi/POIDataSamples.java
+++ b/src/testcases/org/apache/poi/POIDataSamples.java
@@ -44,6 +44,7 @@ public final class POIDataSamples {
private static POIDataSamples _instHPSF;
private static POIDataSamples _instHPBF;
private static POIDataSamples _instHSMF;
+ private static POIDataSamples _instXmlDSign;
private File _resolvedDataDir;
/** true
if standard system propery is not set,
@@ -114,6 +115,12 @@ public final class POIDataSamples {
if(_instHSMF == null) _instHSMF = new POIDataSamples("hsmf");
return _instHSMF;
}
+
+ public static POIDataSamples getXmlDSignInstance(){
+ if(_instXmlDSign == null) _instXmlDSign = new POIDataSamples("xmldsign");
+ return _instXmlDSign;
+ }
+
/**
* Opens a sample file from the test data directory
*
diff --git a/test-data/xmldsign/Office2010-SP1-XAdES-X-L.docx b/test-data/xmldsign/Office2010-SP1-XAdES-X-L.docx
new file mode 100644
index 0000000000000000000000000000000000000000..4aaa772a0dc623b6c510e448d88fcbb7117ee68d
GIT binary patch
literal 23649
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