We are using apache commons mail, specifically the ImageHtmlEmail. We really would like to log every email sent - exactly as it will be sent - in the perfect world it would be something you could paste into sendmail - with all headers and other information included.
This is primarily to troubleshoot some problems we've been having with it turning up as text/plain rather than text/html - but also because it would be nice to have a record of exactly what the system sent out stored in our logs.
So essentially - the dream is a function that would take an ImageHtmlEmail and return a string - as it will be sent. I know I could render it into a string myself, but then I'm bypassing whatever is being done in the library function, which is what we really want to capture. I tried BuildMimeMessage and then getMimeMessage, which I think is probably the correct first step - but that just leaves me with the question of how to turn a mimemessage into a string.
I have a sort of solution - but would love a better one:
/**
* add content of this type
*
* #param builder
* #param content
*/
private static void addContent(final StringBuilder builder, final Object content)
{
try
{
if (content instanceof MimeMultipart)
{
final MimeMultipart multi = (MimeMultipart) content;
for (int i = 0; i < multi.getCount(); i++)
{
addContent(builder, ((MimeMultipart) content).getBodyPart(i));
}
}
else if (content instanceof MimeBodyPart)
{
final MimeBodyPart message = (MimeBodyPart) content;
final Enumeration<?> headers = message.getAllHeaderLines();
while (headers.hasMoreElements())
{
final String line = (String) headers.nextElement();
builder.append(line).append("\n");
}
addContent(builder, message.getContent());
}
else if (content instanceof String)
{
builder.append((String) content).append("\n");
}
else
{
System.out.println(content.getClass().getName());
throw CommonException.notImplementedYet();
}
}
catch (final Exception theException)
{
throw CommonException.insteadOf(theException);
}
}
/**
* get a string from an email
*
* #param email
* #return
*/
public static String fromHtmlEmail(final ImageHtmlEmail email)
{
return fromMimeMessage(email.getMimeMessage());
}
/**
* #param message
* #return a string from a mime message
*/
private static String fromMimeMessage(final MimeMessage message)
{
try
{
message.saveChanges();
final StringBuilder output = new StringBuilder();
final Enumeration<?> headers = message.getAllHeaderLines();
while (headers.hasMoreElements())
{
final String line = (String) headers.nextElement();
output.append(line).append("\n");
}
addContent(output, message.getContent());
return output.toString();
}
catch (final Exception theException)
{
throw CommonException.insteadOf(theException);
}
}
}
Related
I don't have much experience with JAVA mail programming and I need help with one task.
I have this code (this is a part of all code where I load attachments; if you need to see whole code, I can send) and I need to write an attachment size to a new variable
I searched the web and found out that size could be get from getSize() function or by counting bytes of file but I don't know how can I write this codes.
Thanks in advance.
private long analyzeAttachment(Metadata metadata, ContentHandler content, DataPipe data, long messageId) throws IOException, MessagingException{
long attid = IdGenerator.getUniqueID();
Logger.getLogger(ImapMailAnalyzer.class.getName()).log(Level.FINE, "Analyzed attachemnt {0} of message {1}", new Object[]{attid, messageId});
String attName = getAttachmentName(metadata);
data.writeRow(attid, "Abrakadabra", attName, messageId);
writeContent(attid, content, data);
return attid;
}
private String getAttachmentName(Metadata metadata){
if(metadata.get("resourceName") != null){
try {
return MimeUtility.decodeText(metadata.get("resourceName"));
} catch (UnsupportedEncodingException ex) {
Logger.getLogger(getClass().getName()).log(Level.SEVERE, null, ex);
return metadata.get("resourceName");
}
}
return "";
}
The following code uses jakarta.mail-api.
Usually you need to use multipart/xxx content type to send the email with attachments. In this case, you are dealing with javax.mail.internet.MimeMultipart.
So you can get all the body parts and check if they are attachments.
protected void processMimeMultipart(javax.mail.internet.MimeMultipart mimeMultipart) throws Exception {
for(int i = 0; i< mimeMultipart.getCount();i++){
BodyPart bodyPart = mimeMultipart.getBodyPart(i);
int attachmentSize = getAttachmentSize(bodyPart);
}
}
protected int getAttachmentSize(final javax.mail.BodyPart bodyPart) throws Exception {
if(Part.ATTACHMENT.equalsIgnoreCase(bodyPart.getDisposition())) {
return bodyPart.getSize();
}
return -1;
}
I have a .java file in which many methods like the following code exists, how can I refactor to make it cleaner?
public static void e(Throwable tr) {
if (!debug) {
return;
}
if (!allowE) return;
if (tr == null) {
return;
}
String content = wrapContent(tr.getMessage());
StackTraceElement caller = getCallerStackTraceElement();
String tag = generateTag(caller);
customLogger.e(tag, content, tr);
}
public static void i(String content) {
if (!debug) {
return;
}
if (!allowI) return;
content = wrapContent(content);
StackTraceElement caller = getCallerStackTraceElement();
String tag = generateTag(caller);
customLogger.i(tag, content);
}
========update======
This class is a wrapper of android.util.Log, for guys suggesting to follow naming conventions :).
You can strip most of the code into a shared method and then call that from both places.
public static voidcombined(String content, Throwable tr, bool allow)
{
if (!debug) return;
if (!allow) return;
content = wrapContent(content);
StackTraceElement caller = getCallerStackTraceElement();
String tag = generateTag(caller);
if (tr != null) {
customLogger.e(tag, content, tr);
} else {
customLogger.i(tag, content);
}
}
public static void e(Throwable tr) {
if (tr == null) {
return;
}
combined(tr.getMessage(), tr, allowE);
}
public static void i(String content) {
combined(content, null, allowI);
}
if (!debug) {
return;
}
if (!allowE) return;
appears twice, we can put it into a method isAllow()
String content = wrapContent(tr.getMessage());
StackTraceElement caller = getCallerStackTraceElement();
String tag = generateTag(caller);
appears twice too, and customLogger.e and customLogger.i is very alike. Lets put it into method logInLevel().
So, like this:
public static void e(Throwable tr) {
if (!isAllow()||tr==null) return ;
logInLevel(ERROR,tr.getMessage())
}
public static void i(String content) {
if (!isAllow()) return ;
logInLevel(INFO,content);
}
private static boolean isAllow(){
return debug&&allowE;
}
private static logInLevel(int level, String content){
StackTraceElement caller = getCallerStackTraceElement();
String tag = generateTag(caller);
if(level==INFO){
customLogger.i(tag, content);
}
else{//ERROR , you may add more log level here
customLogger.e(tag, content);
}
}
The idea is to apply the separation of concern design principle, to make sure that distinct section of your code are addressing specific concerns.
/**
* Logs error message
* #param tr a {#link Throwable} object containing the message to log
*/
public static void logErrorMessage(Throwable tr) {
if (tr == null) {
return;
}
logContent(tr.getMessage(), allowE, debug, LogLevel.ERROR);
}
/**
* Logs message
* #param content the message to log
* #param allow some variable
* #param debug check the debug status
* #param ll message log level
*/
public static void logContent(String content, boolean allow, boolean debug , LogLevel ll) {
if (!debug) || !allow) return;
switch(ll) {
case ERROR:
customLogger.e(getTag(content), content);
break;
case INFO:
customLogger.i(getTag(content), content);
break;
default:
}
}
/**
* Generates tag
* #param content the message to log
* #return a tagged string
*/
public static String getTag(String content) {
content = wrapContent(content);
return generateTag(getCallerStackTraceElement());
}
I am trying to upload a file into a server through okhttp, I am using a form so using the MultipartBuilder.
OkHttpClient client = new OkHttpClient();
MediaType OCTET_STREAM = MediaType.parse("application/octet-stream");
RequestBody body = new MultipartBuilder()
.type(MultipartBuilder.FORM)
.addPart(Headers.of("Content-Disposition",
"form-data; name=\"breadcrumb\"; filename=\"myfile.bin\"",
"Content-Transfer-Encoding", "binary"),
RequestBody.create(OCTET_STREAM, file))
.build();
Request request = new Request.Builder()
.header("Authorization", cred)
.url(url)
.post(body)
.build();
Response response = client.newCall(request).execute();
But when tracing what goes on the wire:
POST /breadcrumb/ HTTP/1.1
Authorization: Basic aHl6OmhvbGExMjM=
Content-Type: multipart/form-data; boundary=9bc835d6-24b8-42c4-ae8d-5bc89b3fe68f
Transfer-Encoding: chunked
Host: myurl:8000
Connection: Keep-Alive
Accept-Encoding: gzip
10e
--9bc835d6-24b8-42c4-ae8d-5bc89b3fe68f
Content-Disposition: form-data; name="breadcrumb"; filename="myfile.bin"
Content-Transfer-Encoding: binary
Content-Type: application/octet-stream
Content-Length: 21
some file content in binary
--9bc835d6-24b8-42c4-ae8d-5bc89b3fe68f--
0
When I use the traditional Apache http builders, it looks similar, but I don't see the strange characters at the beginning and at the end (10e, 0). Any ideas?
Thanks for your help.
You didn't specified exact Content-Length header, so OkHttpClient started to use chunked transfer encoding.
In HTTP protocol receiver must always know exact length of content(for purpose of allocating memory or other resources) before content will be realy send to server. There is two ways to send it - whole length of content in Content-Length header or by using chucked encoding if content-length can't be calculated at start of request.
This line:
10e
is just says that after that line client will send part of some data with length 0x10e (270) bytes.
The current MultipartBuilder implementation does not support setting a fixed Content-Length. One option would be implementing a FixedMultipartBuilder, which does its magic in the public long contentLength() method. Instead of returning -1L it now computes the length, e.g.:
import com.squareup.okhttp.Headers;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.MultipartBuilder;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.internal.Util;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import okio.BufferedSink;
import okio.ByteString;
/**
* Fluent API to build <a href="http://www.ietf.org/rfc/rfc2387.txt">RFC
* 2387</a>-compliant request bodies.
*/
public final class FixedMultipartBuilder {
private static final byte[] COLONSPACE = { ':', ' ' };
private static final byte[] CRLF = { '\r', '\n' };
private static final byte[] DASHDASH = { '-', '-' };
private final ByteString boundary;
private MediaType type = MultipartBuilder.MIXED;
// Parallel lists of nullable headers and non-null bodies.
private final List<Headers> partHeaders = new ArrayList<>();
private final List<RequestBody> partBodies = new ArrayList<>();
/** Creates a new multipart builder that uses a random boundary token. */
public FixedMultipartBuilder() {
this(UUID.randomUUID().toString());
}
/**
* Creates a new multipart builder that uses {#code boundary} to separate
* parts. Prefer the no-argument constructor to defend against injection
* attacks.
*/
public FixedMultipartBuilder(String boundary) {
this.boundary = ByteString.encodeUtf8(boundary);
}
/**
* Set the MIME type. Expected values for {#code type} are
* {#link com.squareup.okhttp.MultipartBuilder#MIXED} (the default),
* {#link com.squareup.okhttp.MultipartBuilder#ALTERNATIVE},
* {#link com.squareup.okhttp.MultipartBuilder#DIGEST},
* {#link com.squareup.okhttp.MultipartBuilder#PARALLEL} and
* {#link com.squareup.okhttp.MultipartBuilder#FORM}.
*/
public FixedMultipartBuilder type(MediaType type) {
if (type == null) {
throw new NullPointerException("type == null");
}
if (!type.type().equals("multipart")) {
throw new IllegalArgumentException("multipart != " + type);
}
this.type = type;
return this;
}
/** Add a part to the body. */
public FixedMultipartBuilder addPart(RequestBody body) {
return addPart(null, body);
}
/** Add a part to the body. */
public FixedMultipartBuilder addPart(Headers headers, RequestBody body) {
if (body == null) {
throw new NullPointerException("body == null");
}
if (headers != null && headers.get("Content-Type") != null) {
throw new IllegalArgumentException("Unexpected header: Content-Type");
}
if (headers != null && headers.get("Content-Length") != null) {
throw new IllegalArgumentException("Unexpected header: Content-Length");
}
partHeaders.add(headers);
partBodies.add(body);
return this;
}
/**
* Appends a quoted-string to a StringBuilder.
*
* <p>RFC 2388 is rather vague about how one should escape special characters
* in form-data parameters, and as it turns out Firefox and Chrome actually
* do rather different things, and both say in their comments that they're
* not really sure what the right approach is. We go with Chrome's behavior
* (which also experimentally seems to match what IE does), but if you
* actually want to have a good chance of things working, please avoid
* double-quotes, newlines, percent signs, and the like in your field names.
*/
private static StringBuilder appendQuotedString(StringBuilder target, String key) {
target.append('"');
for (int i = 0, len = key.length(); i < len; i++) {
char ch = key.charAt(i);
switch (ch) {
case '\n':
target.append("%0A");
break;
case '\r':
target.append("%0D");
break;
case '"':
target.append("%22");
break;
default:
target.append(ch);
break;
}
}
target.append('"');
return target;
}
/** Add a form data part to the body. */
public FixedMultipartBuilder addFormDataPart(String name, String value) {
return addFormDataPart(name, null, RequestBody.create(null, value));
}
/** Add a form data part to the body. */
public FixedMultipartBuilder addFormDataPart(String name, String filename, RequestBody value) {
if (name == null) {
throw new NullPointerException("name == null");
}
StringBuilder disposition = new StringBuilder("form-data; name=");
appendQuotedString(disposition, name);
if (filename != null) {
disposition.append("; filename=");
appendQuotedString(disposition, filename);
}
return addPart(Headers.of("Content-Disposition", disposition.toString()), value);
}
/** Assemble the specified parts into a request body. */
public RequestBody build() {
if (partHeaders.isEmpty()) {
throw new IllegalStateException("Multipart body must have at least one part.");
}
return new MultipartRequestBody(type, boundary, partHeaders, partBodies);
}
private static final class MultipartRequestBody extends RequestBody {
private final ByteString boundary;
private final MediaType contentType;
private final List<Headers> partHeaders;
private final List<RequestBody> partBodies;
public MultipartRequestBody(MediaType type, ByteString boundary, List<Headers> partHeaders,
List<RequestBody> partBodies) {
if (type == null) throw new NullPointerException("type == null");
this.boundary = boundary;
this.contentType = MediaType.parse(type + "; boundary=" + boundary.utf8());
this.partHeaders = Util.immutableList(partHeaders);
this.partBodies = Util.immutableList(partBodies);
}
#Override public MediaType contentType() {
return contentType;
}
private long contentLengthForPart(Headers headers, RequestBody body) throws IOException {
// Check if the body has an contentLength != -1, otherwise cancel!
long bodyContentLength = body.contentLength();
if(bodyContentLength < 0L) {
return -1L;
}
long length = 0;
length += DASHDASH.length + boundary.size() + CRLF.length;
if (headers != null) {
for (int h = 0, headerCount = headers.size(); h < headerCount; h++) {
length += headers.name(h).getBytes().length
+ COLONSPACE.length
+ headers.value(h).getBytes().length
+ CRLF.length;
}
}
MediaType contentType = body.contentType();
if (contentType != null) {
length += "Content-Type: ".getBytes().length
+ contentType.toString().getBytes().length
+ CRLF.length;
}
length += CRLF.length;
length += bodyContentLength;
length += CRLF.length;
return length;
}
#Override public long contentLength() throws IOException {
long length = 0;
for (int p = 0, partCount = partHeaders.size(); p < partCount; p++) {
long contentPartLength = contentLengthForPart(partHeaders.get(p), partBodies.get(p));
if(contentPartLength < 0) {
// Too bad, can't get contentPartLength!
return -1L;
}
length += contentPartLength;
}
length += DASHDASH.length + boundary.size() + DASHDASH.length + CRLF.length;
return length;
}
#Override public void writeTo(BufferedSink sink) throws IOException {
for (int p = 0, partCount = partHeaders.size(); p < partCount; p++) {
Headers headers = partHeaders.get(p);
RequestBody body = partBodies.get(p);
sink.write(DASHDASH);
sink.write(boundary);
sink.write(CRLF);
if (headers != null) {
for (int h = 0, headerCount = headers.size(); h < headerCount; h++) {
sink.writeUtf8(headers.name(h))
.write(COLONSPACE)
.writeUtf8(headers.value(h))
.write(CRLF);
}
}
MediaType contentType = body.contentType();
if (contentType != null) {
sink.writeUtf8("Content-Type: ")
.writeUtf8(contentType.toString())
.write(CRLF);
}
// Skipping the Content-Length for individual parts
sink.write(CRLF);
partBodies.get(p).writeTo(sink);
sink.write(CRLF);
}
sink.write(DASHDASH);
sink.write(boundary);
sink.write(DASHDASH);
sink.write(CRLF);
}
}
}
Sorry for the long listing... Sadly the MultipartBuilder is final so we have to copy most of the source instead of simply extend it.
For some reason I'm getting null pointer exception. It's downloading the image here and logcat points me to call
public Result call(final String method, final String apiKey, final String... params) {
return call(method, apiKey, map(params));
}
/**
* Performs the web-service call. If the <code>session</code> parameter is
* <code>non-null</code> then an authenticated call is made. If it's
* <code>null</code> then an unauthenticated call is made.<br/>
* The <code>apiKey</code> parameter is always required, even when a valid
* session is passed to this method.
*
* #param method The method to call
* #param apiKey A Last.fm API key
* #param params Parameters
* #param session A Session instance or <code>null</code>
* #return the result of the operation
*/
public Result call(final String method, final String apiKey, Map<String, String> params) {
params = new WeakHashMap<String, String>(params);
InputStream inputStream = null;
// no entry in cache, load from web
if (inputStream == null) {
// fill parameter map with apiKey and session info
params.put(PARAM_API_KEY, apiKey);
try {
final HttpURLConnection urlConnection = openPostConnection(method, params);
inputStream = getInputStreamFromConnection(urlConnection);
if (inputStream == null) {
lastResult = Result.createHttpErrorResult(urlConnection.getResponseCode(),
urlConnection.getResponseMessage());
return lastResult;
}
} catch (final IOException ignored) {
}
}
try {
final Result result = createResultFromInputStream(inputStream);
lastResult = result;
return result;
} catch (final IOException ignored) {
} catch (final SAXException ignored) {
}
return null;
}
It finally cracks at the line "new InputSource(new InputStreamReader(inputStream, "UTF-8")));".
/**
* #param inputStream
* #return
* #throws SAXException
* #throws IOException
*/
private Result createResultFromInputStream(final InputStream inputStream) throws SAXException,
IOException {
final Document document = newDocumentBuilder().parse(
new InputSource(new InputStreamReader(inputStream, "UTF-8")));
final Element root = document.getDocumentElement(); // lfm element
final String statusString = root.getAttribute("status");
final Status status = "ok".equals(statusString) ? Status.OK : Status.FAILED;
if (status == Status.FAILED) {
final Element errorElement = (Element)root.getElementsByTagName("error").item(0);
final int errorCode = Integer.parseInt(errorElement.getAttribute("code"));
final String message = errorElement.getTextContent();
return Result.createRestErrorResult(errorCode, message);
} else {
return Result.createOkResult(document);
}
}
Any ideas? I have no idea what might be wrong. If sufficient info is provided then let me know - I'll get what you need. I'm a beginner. :)
java.net.SocketException: Unexpected end of file from server
The client sends a query to the server by using an URL. I have a HTTPServer that parses info from the URL. Using that info, the server does some work and then returns the response to the client.
Let's say the URL is:
http://localhost:9090/find?term=healthy&term=cooking&count=3
This works fine. But when count is greater or equal to 4, I get java.net.SocketException.
To debug the error, I print out the URL. System.out.println(input);
When count>=4, the URL gets printed two times in the console. Please help.
private final HttpServer server;
public Server(int port){
server = HttpServer.create(new InetSocketAddress(port), MAX_BACKLOG);
server.createContext("/isbn", new ISBNHandler());
server.createContext("/find", new TitleHandler());
}
static class TitleHandler implements HttpHandler {
public void handle(HttpExchange t) throws IOException {
int count=5;
String input=t.getRequestURI().toASCIIString();//get the URL
System.out.println(input);
//using info from URL to do some work
String response = builder.toString();//StringBuilder
// System.out.println(response);
t.sendResponseHeaders(200, response.getBytes().length);
OutputStream os = t.getResponseBody();
os.write(response.getBytes());
os.close();
}
}
Exception
java.net.SocketException: Unexpected end of file from server at
sun.net.www.http.HttpClient.parseHTTPHeader(Unknown Source) at
sun.net.www.http.HttpClient.parseHTTP(Unknown Source) at
sun.net.www.http.HttpClient.parseHTTPHeader(Unknown Source) at
sun.net.www.http.HttpClient.parseHTTP(Unknown Source) at
sun.net.www.protocol.http.HttpURLConnection.getInputStream(Unknown
Source) at java.net.HttpURLConnection.getResponseCode(Unknown Source)
at TestHarness.assertJSONResponse(TestHarness.java:101) at
TestHarness.testServer(TestHarness.java:38) at
TestHarness.main(TestHarness.java:25)
TestHarness
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Sample main method with initial acceptance tests to help you along
*/
public class TestHarness {
private static Pattern MAP_PATTERN = Pattern.compile("(['\"])(.*?)\\1:\\s*(['\"])(.*?)\\3");
private static Pattern LIST_PATTERN = Pattern.compile("\\{(.*?)\\}");
public static void main(String[] args) throws IOException {
/*
if (args.length < 1) {
System.out.println("The test harness requires a single parameter: the location of the CSV file to parse");
}
*/
BookServer server = new BookServer(9090, new File("books.csv"));
server.start();
testServer();
server.stop();
}
/**
* Run initial acceptance tests
*/
#SuppressWarnings("unchecked")
private static void testServer() {
assertJSONResponse("Book Test", "http://localhost:9090/isbn/9780470052327",
createMap("isbn", "9780470052327", "title", "Techniques of Healthy Cooking", "author", "Mary Dierdre Donovan", "publisher", "Wiley", "publishedYear", "2007"));
assertJSONResponse("Book Test", "http://localhost:9090/isbn/9780451169525",
createMap("isbn", "9780451169525", "title", "Misery", "author", "Stephen King", "publisher", "Signet", "publishedYear", "1987"));
assertJSONResponse("Query Test", "http://localhost:9090/find?term=healthy&term=cooking&count=4",
Arrays.asList(createMap("isbn", "9780470052327", "title", "Techniques of Healthy Cooking", "author", "Mary Dierdre Donovan", "publisher", "Wiley", "publishedYear", "2007")));
}
/**
* Helper method to convert the vararg parameters into a Map. Assumes alternating key, value, key, value... and calls
* toString on all args
*
* #param args the parameters to put in the map, alternating key ancd value
* #return Map of String representations of the parameters
*/
private static Map<String, String> createMap(Object ... args) {
Map<String, String> map = new HashMap<String, String>();
for (int i=0; i < args.length; i+=2) {
map.put(args[i].toString(), args[i+1].toString());
}
return map;
}
/**
* Parses a JSON list of maps
* NOTE: assumes all keys and values in the nested maps are quoted
*
* #param content the JSON representation
* #return a list of parsed Map content
*/
private static List<Map<String, String>> parseJSONList(CharSequence content) {
List<Map<String, String>> list = new ArrayList<Map<String, String>>();
Matcher m = LIST_PATTERN.matcher(content);
while(m.find()) {
list.add(parseJSONMap(m.group(1)));
}
return list;
}
/**
* Parse JSON encoded content into a Java Map.
* NOTE: Assumes that all elements in the map are quoted
*
* #param content the JSON representation to be parsed
* #return A map of parsed content
*/
private static Map<String, String> parseJSONMap(CharSequence content) {
Map<String, String> map = new HashMap<String, String>();
Matcher m = MAP_PATTERN.matcher(content);
while (m.find()) {
map.put(m.group(2), m.group(4));
}
return map;
}
/**
* Retrieve content from a test URL and assert that its content is the expected. Results will be printed to System.out for convenience
*
* #param testName Name of the test, to be used simply for labelling
* #param urlString The URL to test
* #param expected The content expected at that URL
*/
private static void assertJSONResponse(String testName, String urlString, Object expected) {
try {
URL url = new URL(urlString);
HttpURLConnection con = ((HttpURLConnection)url.openConnection());
if (!assertTest(testName + " - response code", con.getResponseCode(), 200)) return;
StringBuilder b = new StringBuilder();
BufferedReader r = new BufferedReader(new InputStreamReader(con.getInputStream()));
String line;
while((line = r.readLine()) != null) b.append(line);
String result = b.toString();
assertTest(testName + " - content retrieved", !result.isEmpty(), true);
Object parsed = result.trim().startsWith("[") ? parseJSONList(result) : parseJSONMap(result);
assertTest(testName + " - parsed content match", parsed, expected);
} catch (Exception e) {
System.out.println(testName + ": <<<FAILED with Exception>>>");
e.printStackTrace(System.out);
}
}
/**
* Log the results of a test assertion
*
* #param testName Name of the test, to be used simply for labelling
* #param result The result of the operation under test
* #param expected The expected content that the result will be compared against
* #return whether the test was successful
*/
private static boolean assertTest(String testName, Object result, Object expected) {
boolean passed = result.equals(expected);
System.out.println(testName + (passed ? ": <<<PASSED>>>" : String.format(": <<<FAILED>>> expected '%s' but was '%s'", expected, result)));
return passed;
}
}
I fixed the error. The server calls a private sorting function, but I made a bug in the sorting function. The server crashed.