io.openliberty.arquillian.managed.exceptions.SupportFeatureTextExceptionLocator Maven / Gradle / Ivy
Show all versions of arquillian-liberty-managed-jakarta Show documentation
/*
* Copyright 2018, 2022 IBM Corporation, Red Hat Middleware LLC, and individual contributors
* identified by the Git commit log.
*
* Licensed 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 io.openliberty.arquillian.managed.exceptions;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import io.openliberty.arquillian.managed.exceptions.NestedExceptionBuilder.ExMsg;
/**
* Tries to receive information about an exception from the server in text form
*
* This relies on the liberty-support-jakarta-feature being installed and running
*
* The text format expected from the server includes information about the
* exception and its cause chain.
*
* For each exception in the cause chain, the following information is returned:
*
* - the class name
* - the names of all superclasses (if any)
* - the exception message
*
*
* Example:
*
*
*
* exClass com.example.Exception
* exSuperclass com.example.SuperclassOfException
* exSuperclass com.example.SuperclassOfSuperclassOfException
* This is the exception message
* which can have multiple lines
* exClass com.example.CauseOfException
* Exception cause message
* ...
*
*
*
* This strategy for retrieving exceptions does not retain as much information
* as retrieving a serialized object (in particular the stack trace is lost) but
* it's much more robust against missing classes on the client. If one class in
* the cause chain can't be loaded, other classes in the chain can still be
* loaded. In addition, if a class in can't be loaded, we also attempt to load
* classes in its type hierarchy instead. This should catch most cases where a
* test requires that a spec exception is thrown but liberty throws an exception
* which subclasses it. The subclass may not be available to the client but the
* spec exception must be (since it was referenced from the test).
*
*/
public class SupportFeatureTextExceptionLocator implements DeploymentExceptionLocator {
private final static Logger log = Logger.getLogger(SupportFeatureTextExceptionLocator.class.getName());
private final MBeanServerConnection mbsc;
private final ObjectName on;
private static final Pattern CLASS_PATTERN = Pattern.compile("exClass (.*)");
private static final Pattern SUPERCLASS_PATTERN = Pattern.compile("exSuperclass (.*)");
public SupportFeatureTextExceptionLocator(MBeanServerConnection mbsc) {
this.mbsc = mbsc;
StringBuilder sb = new StringBuilder("LibertyArquillian:");
sb.append("type=").append("DeploymentExceptionMBean");
try {
on = new ObjectName(sb.toString());
} catch (MalformedObjectNameException e) {
// Shouldn't happen as most of the URI parts are hard coded
throw new IllegalArgumentException("Invalid ObjectName: " + sb.toString() + " " + e, e);
}
}
@Override
public Throwable getException(String appName, String logLine, long deploymentTime) {
try {
Object[] response = (Object[]) mbsc.invoke(on, "getDeploymentException", new String[] { appName, "text" },
new String[] { String.class.getName(), String.class.getName() });
int status = ((Integer) response[0]).intValue();
if (status == 400) {
log.warning("After " + appName + " failed to start, the server did not report an exception for that app");
} else if (status != 200) {
log.info("Unable to receive text format exception from server, is usr:arquillian-support-jakarta-2.1 installed?");
} else {
log.finer("Reading exception returned from server");
try (BufferedReader reader = new BufferedReader(new StringReader((String) response[1]))) {
return readResponse(reader);
}
}
} catch (IOException ex) {
log.warning("IO Exception trying to get text exception: " + ex);
} catch (Exception ex) {
log.warning("Unexpected exception thrown while trying to get text exception: " + ex);
}
return null;
}
private Throwable readResponse(BufferedReader reader) throws IOException {
String line;
ResponseReader responseReader = new ResponseReader();
while ((line = reader.readLine()) != null) {
Matcher classMatcher = CLASS_PATTERN.matcher(line);
if (classMatcher.matches()) {
responseReader.readClass(classMatcher.group(1));
continue;
}
Matcher superclassMatcher = SUPERCLASS_PATTERN.matcher(line);
if (superclassMatcher.matches()) {
responseReader.readSuperclass(superclassMatcher.group(1));
continue;
}
responseReader.readMessage(line);
}
responseReader.finishMsg();
List msgs = responseReader.finish();
return NestedExceptionBuilder.buildNestedException(msgs);
}
private static class ResponseReader {
List msgs = new ArrayList<>();
ExMsg currentMsg = null;
StringBuilder messageBuilder = null;
private void readClass(String className) {
log.finer("Read class " + className);
finishMsg();
currentMsg.exName = className;
}
private void readSuperclass(String className) {
log.finer("Read superclass" + className);
currentMsg.superclasses.add(className);
}
private void readMessage(String message) {
log.finer("Read message " + message);
if (messageBuilder == null) {
messageBuilder = new StringBuilder();
} else {
messageBuilder.append("\n");
}
messageBuilder.append(message);
}
private void finishMsg() {
if (currentMsg != null) {
msgs.add(currentMsg);
if (messageBuilder != null) {
currentMsg.logMsg = messageBuilder.toString();
}
}
currentMsg = new ExMsg();
messageBuilder = null;
}
private List finish() {
return msgs;
}
}
}