How to handle XML-related exceptions in Java? - java

I'm working on an appliation that needs to read, manipulate and write XML documents. While working with the XML API, I had to catch several exceptions, and I'm not sure how to handle them.
Consider the following code:
public static void removeWhitespace(Document document)
{
XPath xPath = getXPath();
NodeList emptyTextNodes;
try
{
XPathExpression expression = xPath.compile("//text()[normalize-space(.) = '']");
emptyTextNodes = (NodeList) expression.evaluate(document, XPathConstants.NODESET);
}
catch (XPathExpressionException e)
{
// How to handle this?
return;
}
int nEmptyTextNodes = emptyTextNodes.getLength();
for (int i = 0; i < nEmptyTextNodes; ++i)
{
Node node = emptyTextNodes.item(i);
node.getParentNode().removeChild(node);
}
}
The documentation for XPath#compile says that XPathExpressionException will be thrown "If expression cannot be compiled". However, in this case, the expression is hardcoded and (presumably) valid, so this shouldn't happen. Since this is a checked exception, I have to handle it - but what should I do in the catch block?
Similarly, the documentation for XPathExpression#evaluate says that XPathExpressionException will be thrown "If the expression cannot be evaluated". Since I believe the expression is valid, the only way I think this could happen is if the caller passes an invalid Document. How should I handle that?
Consider this method:
public static String documentToString(Document document)
{
StringWriter writer = new StringWriter();
try
{
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
transformer.transform(new DOMSource(document), new StreamResult(writer));
}
catch (TransformerException e)
{
// What happens now?
return null;
}
String xml = writer.getBuffer().toString();
// No need to close the StringWriter
return xml;
}
TransformerFactory#newTransformer will throw a TransformerConfigurationException (would have linked the docs but SO won't let me) "When it is not possible to create a Transformer instance". How can this ever happen? How do I deal with it?
I could declare these exceptions to be thrown, but that would just move the problem somewhere else.
Under which circumstance will these exceptions be thrown, and how should I handle them?

Under which circumstance will these exceptions be thrown
These are possible causes for XML / XPath / XSLT related exceptions in the javax.xml packages:
invalid arguments passed to API methods
use of configurations which are optional and not supported by implementations
IO and encoding errors when reading a Source or writing a Result
Syntax errors when parsing a XML document, a XSLT stylesheet, a XPath expression
errors caused by executing an invalid operation (e.g. in XPath: calling a node-set function on a string or a number)
any other runtime error, most likely caused by a bug in the implementation
how should I handle them?
Like you treat all exceptions:
If can handle an exception, i.e. fix its cause or take some other action, then catch it and run the exception handler code in the catch block.
If you can't handle an exception let the caller handle it. For checked exceptions the simplest way is just to declare it in your methods throw clause.
You could also catch an exception to convert it to another type (another checked exception, a RuntimeException or even an Error). Converting to a RuntimeException (e.g. a IllegalStateException) can be used if you don't want to have a checked exception in the method signature.
To your examples:
There is probably no possibility that your methods will fail (No disk IO operations, no syntax errors, all arguments ok, etc.).
Still you can't handle the exception so you need to pass it to the caller.
In the case of the two methods it could be irritating (but still justifiable) if the methods would declare XPathExpressionException and TransformerException.
If you ask me I would catch the XPathExpressionException and TransformerException and rethrow them wrapped into a IllegalStateException.

This shouldn't happen [but], I have to handle it[, so] what should I do in the catch block?
If you really believe that it can not happen, If you are counting on it not to happen, and it turns out that you were wrong and the exception actually does happen, then that means your program is defective.
Here's the simplest handler for an exception that means the program is defective:
catch (TheException ex) {
assert false;
}
Here's a better one:
import org.apache.log4j.Logger;
...
static final Logger LOGGER = ...;
...
catch (TheException ex) {
LOGGER.fatal("Unexpected exception", ex);
myApplication.makeSafeToShutdown(...);
System.exit(1);
}

Related

No NullPointerException on the logs

I have a strange behaviour in my java code I would like to ask some advice.
In a multithreading application I wrote this code:
scratchDir.resolve(directoryTree).toFile().mkdirs();
For a bug the Object scratchDir is null, I was expecting a stack trace on the logs but there's nothing about the error.
I have checked the code and I never try to catch the NullPointerException.
Here is the complete method code:
#Override
public void write(JsonObject jsonObject) throws FileSystemException {
Path directoryTree = getRelativePath();
scratchDir.resolve(directoryTree).toFile().mkdirs();
String newFileName = getHashFileName(jsonObject);
Path filePath = scratchDir.resolve(directoryTree).resolve(newFileName);
logger.debug("Write new file Json {} to persistent storage dir {}", newFileName, scratchDir);
File outputFile = filePath.toFile();
if (outputFile.exists()) {
throw new FileAlreadyExistsException(filePath.toString());
}
try (FileWriter fileWriter = new FileWriter(outputFile)) {
fileWriter.write(jsonObject.toString());
fileWriter.flush();
} catch (Exception e) {
logger.error(e);
}
}
Why I don't have the exception in my logs?
Why are you doing this?
The proper way to do this is:
Files.createDirectories(scratchDir.resolve(directoryTree));
don't mix old and new API. The old mkdirs() api DEMANDS that you check the return value; if it is false, the operation failed, and you do not get the benefit of an exception to tell you why. This is the primary reason for why there is a new API in the first place.
Are you sure you aren't confused - and that is the actual problem? The line as you have it will happily do absolutely nothing whatsoever (no directories, and no logs or exceptions). The line above will throw if it can't make the directories, so start there.
Then, if that line IS being run and nothing is logged, then you've caught the NPE and discarded it, someplace you didn't paste.

How do you write a JUnit Test for TransformerException from javax.xml.transform.Transformer.transform

I have been trying to write a unit test in an attempt to reach full coverage on my class under test.
I am trying to test that it properly catches the TransformerException thrown from this method in class DOMUtil:
public final class DOMUtil
{
// This class is entirely static, so there's no need to ever create an instance.
private DOMUtil()
{
// Do nothing.
}
...
...
/**
* Returns a String containing XML corresponding to a Document. The String
* consists of lines, indented to match the Document structure.
* #param doc - Document to be converted.
* #return String containing XML or null if an error occurs.
*/
public static String documentToString(final Document doc)
{
try
{
// Note that there is no control over many aspects of the conversion,
// e.g., insignificant whitespace, types of quotes.
final Transformer tf = TransformerFactory.newInstance().newTransformer();
tf.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
tf.setOutputProperty(OutputKeys.INDENT, "yes");
final Writer out = new StringWriter();
tf.transform(new DOMSource(doc), new StreamResult(out));
return out.toString();
}
catch (final TransformerException e)
{
LOG.error("Error converting Document to String: " + e.getMessage());
return null;
}
}
}
My DOMUtilTest class:
#RunWith(PowerMockRunner.class)
...
...
#PrepareForTest({Document.class, TransformerFactory.class, Transformer.class, DOMUtil.class, DocumentBuilder.class, DocumentBuilderFactory.class})
public class DOMUtilTest
{
/**
* Test documentToString with TransformerException
*/
#Test(expected=TransformerException.class)
public void testDocumentToStringTransformerException()
{
try
{
// Mocking Stuff
TransformerFactory fac = PowerMockito.mock(TransformerFactory.class);
Transformer transformer = PowerMockito.mock(Transformer.class);
// probably only need two of these??
PowerMockito.whenNew(TransformerFactory.class).withNoArguments().thenReturn(fac);
PowerMockito.whenNew(Transformer.class).withNoArguments().thenReturn(transformer);
PowerMockito.when(fac.newTransformer(ArgumentMatchers.any(Source.class))).thenReturn(transformer);
PowerMockito.when(fac.newTransformer()).thenReturn(transformer);
// spy in results
PowerMockito.spy(transformer);
PowerMockito.when(transformer, "transform", ArgumentMatchers.any(Source.class), ArgumentMatchers.any(Result.class)).thenThrow(new TransformerException("Mocked TransformerException"));
final String result = DOMUtil.documentToString(doc);
LOG.info("result length: " + result.length());
}
catch(Exception e)
{
LOG.error("Exception in testDocumentToStringTransformerException: " + e.getMessage());
fail("Exception in testDocumentToStringTransformerException: " + e.getMessage());
}
}
}
I feel like I have tried every possible solution. I have a lot of working tests with similar conditions on other classes/methods. I have tried
annotations style mocking/injecting
spying
ArgumentMatchers.any(DOMSource.class), ArgumentMatchers.any(StreamResult.class) (which gives error: The method any(Class) from the type ArgumentMatchers refers to the missing type DOMSource)
and every other possible way I could think of. Right now the result is still showing: result length: 25706 (the real length of the doc object) without getting an exception before copying the document.
Here is a further explanation of TransformationException from Java 7 API - Exceptions and Error Reporting.
TransformerException is a general exception that occurs during the
course of a transformation. A transformer exception may wrap another
exception, and if any of the TransformerException.printStackTrace()
methods are called on it, it will produce a list of stack dumps,
starting from the most recent. The transformer exception also provides
a SourceLocator object which indicates where in the source tree or
transformation instructions the error occurred.
TransformerException.getMessageAndLocation() may be called to get an
error message with location info, and
TransformerException.getLocationAsString() may be called to get just
the location string.
My assumption is the problem is with mocking of the line:
final Transformer tf = TransformerFactory.newInstance().newTransformer();
Has anybody encountered a situation like this, with JUnit and PowerMockito?
If anybody could point me in the right direction or show me how to solve this any help would be greatly appreciated.
Thanks!
In theory this does not seem like the prettiest way of mocking the TransformerException, but upon a lot of research and attempts, I came up with the solution of mocking the Result outputTarget argument of tf.transform:
#Test
public void testDocumentToStringTransformerException() throws Exception
{
// Mocking Stuff
PowerMockito.whenNew(StringWriter.class).withNoArguments().thenReturn(null);
// Execute Method Under Test
final String result = DOMUtil.documentToString(doc);
// Verify Result
assertNull(result);
}
Which will ultimately give:
XML-22122: (Fatal Error) Invalid StreamResult - OutputStream, Writer, and SystemId are null.
encapsulated into a TransformerException. This allows me to test the TransformerException branches of code coverage.
So the good news is it is possible with mocking, instead of trying to figure out how to manipulate a Document object to throw the exception. Also don't have to mock as many objects as I initially thought.
(References to TransformerException here: Java 7 API - Exceptions and Error Reporting)

javax.xml.transform.TransformerFactory Unicode issue- Java

We are not able transform Unicode Characters properly. We are giving input in XML format, when we try to transform we are not able to get back the original string.
This is the code i'm using,
StringCarrier OStringCarrier = new StringCarrier();
String SXmlFileData= "<export_candidate_response><criteria><output><lastname>Bhagavath</lastname><firstname>ガネーシュ</firstname></output></export_candidate_response>";
String SResult = "";
try
{
TransformerFactory tFactory = TransformerFactory.newInstance();
Transformer transformer = tFactory.newTransformer(new StreamSource(SXslFileName));
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF8");
OutputStream xmlResult = (OutputStream)new ByteArrayOutputStream();
StreamResult outResult = new StreamResult(xmlResult);
transformer.transform(new StreamSource(
new ByteArrayInputStream(SXmlFileData.getBytes("UTF8"))),outResult);
SResult = outResult.getOutputStream().toString();
}
catch (TransformerConfigurationException OException)
{
//Exception has been thrown
OException.printStackTrace();
return OStringCarrier;
}
catch (TransformerException OException)
{
//Exception has been thrown
OException.printStackTrace();
return OStringCarrier;
}
catch (Exception OException)
{
//Exception has been thrown
OException.printStackTrace();
return OStringCarrier;
}
This is the output i'm getting ガãƒ?ーシュ in place of ガネーシュ
This is the output i'm getting ガãƒ?ーシュ in place of ガネーシュ
That tells you that somewhere in this process, data in UTF-8 is being read by a piece of software that thinks it is reading Latin-1. What it doesn't tell you is where in the process this is happening. So you need to divide-and-conquer - you need to find the last point at which the data is correct.
Start by establishing whether the problem is before the transformation or after it. That's very easy if you're using an XSLT 2.0 processor: you can use ` to see what string of characters the XSLT processor has been given. It's a bit trickier with a 1.0 processor, but you can use substring($in, $n, 1) to extract the nth character, and that should give you a clue.
My suspicion is that it's the input. Firstly, putting non-ASCII characters in a Java string literal is always a bit dangerous, because the round trip to a source repository can easily corrupt the code if you're not very careful about everything being configured correctly. Secondly, if the string is correct, it would be much safer to read it using a StringReader, rather than converting it to a byte stream. Try:
transformer.transform(new StreamSource(
new StringReader(SXmlFileData)),outResult);

How apply CDATA to transformer parameter with jdom

For some reason I have tried to surround the parameters sExtraParameter, sExtraParameter2, sExtraParameter3 with <![CDATA[ ]]> string in order to get "pretty-printed" latin characters. But every time I check the xml output, it stills show bad parsed characters.
So, if is there another way to apply the CDATA to this parameters?
public static Element xslTransformJDOM(File xmlFile, String xslStyleSheet, String sExtraParameter, String sExtraParameterValue, String sExtraParameter2, String sExtraParameterValue2, String sExtraParameter3,String sExtraParameterValue3 ) throws JDOMException, TransformerConfigurationException, FileNotFoundException, IOException{
try{
Transformer transformer = TransformerFactory.newInstance().newTransformer(new StreamSource(xslStyleSheet));
transformer.setParameter(sExtraParameter, sExtraParameterValue);
transformer.setParameter(sExtraParameter2, sExtraParameterValue2);
transformer.setParameter(sExtraParameter3, sExtraParameterValue3);
JDOMResult out = new JDOMResult();
transformer.transform(new StreamSource(xmlFile), out);
Element result = out.getDocument().detachRootElement();
setSize(new XMLOutputter().outputString(result).length());
return result;
}
catch (TransformerException e){
throw new JDOMException("XSLT Transformation failed", e);
}
}
edit:
I am following up a project from my boss, for these reason I have not the entire code to show you here.
Maybe I have missed the question, but the API (http://docs.oracle.com/javaee/1.4/api/javax/xml/transform/Transformer.html#setParameter(java.lang.String, java.lang.Object)) for setParameter does not expect
value - The value object. This can be any valid Java object. It is up to the processor to provide the proper object coersion or to simply pass the object on for use in an extension.
This could then vary by implementation, assuming you are using JDOM.
There may be a CDATA xml element that would then be processed correctly. Maybe: http://www.jdom.org/docs/apidocs/org/jdom2/CDATA.html
You could still think about setting the serializer settings to some sort of whitespace preservation. http://www.jdom.org/docs/apidocs.1.1/org/jdom/output/Format.TextMode.html

SAXBuilder build throws java.lang.StringIndexOutOfBoundsException

I am parsing this xml
<Root><Status>1</Status><Message>Get call Successful</Message><StatusCode></StatusCode><Item type = 'all' subtype = '0' ><subItem><rank>0</rank><name>humywe12</name><value>4500</value></subItem></Item></Root>
I am parsing it using this code
SAXBuilder builder = new SAXBuilder();
Document doc = null;
xml = xml.replaceAll("\t", "");
StringReader r = new StringReader(xml);
try {
doc = builder.build(r); <-----here it throws error
} catch (IOException e) {
// e.printStackTrace();
throw e;
} catch (Exception e) {
// e.printStackTrace();
throw e;
}
return doc;
}
builder.build(r) it throws exception StringIndexOutOfBoundsException.
Am I doing something wrong?
updated
ok I have removed only these tags "type = 'all' subtype = '0'" and now it is not giving java.lang.StringIndexOutOfBoundsException. Is there any problem with SAXBUILDER ??
I believe this was a know JDom bug. See http://www.jdom.org/pipermail/jdom-interest/2000-August/001227.html
You may want to check out one of the latest versions of jdom (as fits within your application).
Someone can try and identify the error for you, but what I would do is to start with very small xml, say
<Root></Root>
and keep adding to it till I get the error and then see what in the data caused the error.
Spaces are not allowed between the attribute name and the "=", or between the "=" and the attribute value.
See the spec.

Categories