Skip to content
Snippets Groups Projects
Commit 97b5e1df authored by Nils Christian Ehmke's avatar Nils Christian Ehmke
Browse files

Removed unnecessary code and replaced some code parts with Mirror; Continued...

Removed unnecessary code and replaced some code parts with Mirror; Continued with the cockpit; Fixed some minor bugs; Modified the logging in order to use only a log file.
parent 8c6cacd1
No related branches found
No related tags found
No related merge requests found
......@@ -15,7 +15,6 @@
***************************************************************************/
package kieker.webgui.common;
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
......@@ -28,7 +27,6 @@ import kieker.analysis.display.HtmlText;
import kieker.analysis.display.Image;
import kieker.analysis.display.PlainText;
import kieker.analysis.display.annotation.Display;
import kieker.analysis.model.analysisMetaModel.MIProject;
import kieker.analysis.plugin.AbstractPlugin;
import kieker.analysis.plugin.annotation.InputPort;
import kieker.analysis.plugin.annotation.OutputPort;
......@@ -80,18 +78,7 @@ public class ClassAndMethodContainer {
private Class<?> plainTextClass;
private Class<?> htmlTextClass;
private Method repositoryConfigurationMethod;
private Method plainTextgetTextMethod;
private Method analysisControllerWithMappingGetController;
private Method analysisControllerWithMappingGetMapping;
private Method analysisControllerCreateAnalysisController;
private Method analysisControllerThreadJoin;
private Method analysisControllerGetState;
private Method analysisControllerLoadFromFile;
private Method pluginProgrammaticOnlyMethod;
private Method repositoryProgrammaticOnlyMethod;
private Method logImplWebguiLoggingClassGetEntriesMethod;
private Constructor<?> analysisControllerThreadConstructor;
......@@ -109,7 +96,6 @@ public class ClassAndMethodContainer {
this.loadClasses(classLoader);
this.loadMethods();
this.loadSpecialCases(classLoader);
this.loadConstructors();
} catch (final ClassNotFoundException ex) {
......@@ -136,33 +122,10 @@ public class ClassAndMethodContainer {
this.analysisControllerThreadConstructor = this.analysisControllerThreadClass.getConstructor(this.analysisControllerClass);
}
private void loadSpecialCases(final ClassLoader classLoader) throws ClassNotFoundException, SecurityException, NoSuchMethodException {
// This is a special case as we need to load some additional classes to search for the correct method
final Class<?> miProjectClass = classLoader.loadClass(MIProject.class.getName());
this.analysisControllerLoadFromFile = this.analysisControllerClass.getMethod("loadFromFile", File.class);
this.analysisControllerCreateAnalysisController = this.analysisControllerClass.getMethod("createAnalysisController", miProjectClass, ClassLoader.class);
// Another special case as the parameter is a long
final Method[] methods = this.analysisControllerThreadClass.getMethods();
Method joinMethod = null;
for (final Method method : methods) {
if ("join".equals(method.getName())) {
joinMethod = method;
}
}
this.analysisControllerThreadJoin = joinMethod;
}
private void loadMethods() throws SecurityException, NoSuchMethodException {
// The following part has to be done carefully: The methods will be loaded via the name
this.repositoryConfigurationMethod = this.repositoryAnnotationClass.getMethod("configuration", new Class<?>[0]);
this.plainTextgetTextMethod = this.plainTextClass.getMethod("getText", new Class<?>[0]);
this.analysisControllerWithMappingGetController = this.analysisControllerWithMappingClass.getMethod("getController", new Class<?>[0]);
this.analysisControllerWithMappingGetMapping = this.analysisControllerWithMappingClass.getMethod("getPluginMap", new Class<?>[0]);
this.analysisControllerGetState = this.analysisControllerClass.getMethod("getState", new Class<?>[0]);
this.pluginProgrammaticOnlyMethod = this.pluginAnnotationClass.getMethod("programmaticOnly", new Class<?>[0]);
this.repositoryProgrammaticOnlyMethod = this.repositoryAnnotationClass.getMethod("programmaticOnly", new Class<?>[0]);
this.logImplWebguiLoggingClassGetEntriesMethod = this.logImplWebguiLoggingClass.getMethod("getEntries", String.class);
}
......@@ -253,14 +216,6 @@ public class ClassAndMethodContainer {
return this.htmlTextClass;
}
public Method getPlainTextgetTextMethod() {
return this.plainTextgetTextMethod;
}
public Method getAnalysisControllerWithMappingGetMapping() {
return this.analysisControllerWithMappingGetMapping;
}
public Class<?> getAnalysisControllerClass() {
return this.analysisControllerClass;
}
......@@ -273,18 +228,6 @@ public class ClassAndMethodContainer {
return this.analysisControllerThreadClass;
}
public Method getAnalysisControllerCreateAnalysisController() {
return this.analysisControllerCreateAnalysisController;
}
public Method getAnalysisControllerWithMappingGetController() {
return this.analysisControllerWithMappingGetController;
}
public Method getAnalysisControllerThreadJoin() {
return this.analysisControllerThreadJoin;
}
public Method getAnalysisControllerGetState() {
return this.analysisControllerGetState;
}
......@@ -293,18 +236,6 @@ public class ClassAndMethodContainer {
return this.analysisControllerThreadConstructor;
}
public Method getAnalysisControllerLoadFromFile() {
return this.analysisControllerLoadFromFile;
}
public Method getPluginProgrammaticOnlyMethod() {
return this.pluginProgrammaticOnlyMethod;
}
public Method getRepositoryProgrammaticOnlyMethod() {
return this.repositoryProgrammaticOnlyMethod;
}
public Class<?> getLogImplWebguiLoggingClass() {
return this.logImplWebguiLoggingClass;
}
......
......@@ -70,6 +70,9 @@ import kieker.webgui.persistence.impl.util.SimpleCastFunction;
import org.primefaces.model.UploadedFile;
import net.vidageek.mirror.dsl.Mirror;
import net.vidageek.mirror.exception.MirrorException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
......@@ -375,14 +378,13 @@ public class FSProjectDAOImpl implements IProjectDAO, ReleaseListener {
throw new IOException("Project is null");
}
// Load the project
final Object project = ClassAndMethodContainer.invokeClassMethod(classAndMethodContainer.getAnalysisControllerLoadFromFile(), null,
FSProjectDAOImpl.assembleKaxFile(projectName));
if (project == null) {
throw new IOException("Project could not be loaded.");
} else {
try {
// Load the project
final Object project = new Mirror().on(classAndMethodContainer.getAnalysisControllerClass()).invoke().method("loadFromFile")
.withArgs(FSProjectDAOImpl.assembleKaxFile(projectName));
return project;
} catch (final MirrorException ex) {
throw new IOException("Project could not be loaded.", ex);
}
}
......
......@@ -230,9 +230,9 @@ public class ACManager {
* The project to update.
*/
public void updateDisplays(final String projectName) {
// if (this.analyses.containsKey(projectName)) {
// this.analyses.get(projectName).updateDisplays();
// }
if (this.analyses.containsKey(projectName)) {
this.analyses.get(projectName).updateDisplays();
}
}
}
......@@ -17,7 +17,11 @@
package kieker.webgui.service.impl.util;
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import kieker.analysis.AnalysisController;
import kieker.common.logging.Log;
......@@ -45,8 +49,11 @@ public class Analysis {
private final Object analysisController;
private final Object analysisControllerThread;
// private final Map<String, Map<String, Object>> displayObjects = new HashMap<String, Map<String, Object>>();
// private final Map<String, Map<String, Method>> displayMethods = new HashMap<String, Map<String, Method>>();
private Map<Object, Object> pluginMap;
private final Map<String, Map<String, Object>> displayPlugins = new HashMap<String, Map<String, Object>>();
private final Map<String, Map<String, Object>> displayObjects = new HashMap<String, Map<String, Object>>();
private final Map<String, Map<String, Method>> displayMethods = new HashMap<String, Map<String, Method>>();
/**
* Creates a new instance of this class using the given parameters.
......@@ -63,26 +70,71 @@ public class Analysis {
public Analysis(final ClassLoader classLoader, final File projectFile) throws InvalidAnalysisStateException, AnalysisInitializationException {
try {
this.classAndMethodContainer = new ClassAndMethodContainer(classLoader);
final Method createMethod = this.classAndMethodContainer.getAnalysisControllerCreateAnalysisController();
final Object modelProject = ClassAndMethodContainer.invokeClassMethod(this.classAndMethodContainer.getAnalysisControllerLoadFromFile(), null,
projectFile.getAbsoluteFile());
final Object controllerAndMapping = ClassAndMethodContainer.invokeClassMethod(createMethod, null, modelProject, classLoader);
this.analysisController = ClassAndMethodContainer.invokeMethod(this.classAndMethodContainer.getAnalysisControllerWithMappingGetController(),
controllerAndMapping, null);
this.analysisControllerThread = ClassAndMethodContainer.invokeConstructor(this.classAndMethodContainer.getAnalysisControllerThreadConstructor(),
this.analysisController);
// this.updateDisplaysThread = new UpdateDisplaysThread(); // ClassAndMethodContainer.invokeMethod(
// this.classAndMethodContainer.getAnalysisControllerWithMappingGetMapping(), controllerAndMapping, null));
if ((this.analysisController == null) || (this.analysisControllerThread == null)) {
throw new AnalysisInitializationException("An error occured while instantiating the analysis.");
final Class<?> analysisControllerClass = this.classAndMethodContainer.getAnalysisControllerClass();
final Class<?> analsisControllerThreadClass = this.classAndMethodContainer.getAnalysisControllerThreadClass();
final Object modelProject = new Mirror().on(analysisControllerClass).invoke().method("loadFromFile").withArgs(projectFile.getAbsoluteFile());
final Object controllerAndMapping = new Mirror().on(analysisControllerClass).invoke().method("createAnalysisController")
.withArgs(modelProject, classLoader);
this.analysisController = new Mirror().on(controllerAndMapping).invoke().method("getController").withoutArgs();
this.analysisControllerThread = new Mirror().on(analsisControllerThreadClass).invoke().constructor().withArgs(this.analysisController);
this.pluginMap = (Map<Object, Object>) new Mirror().on(controllerAndMapping).invoke().method("getPluginMap").withoutArgs();
final List<Object> views = (List<Object>) new Mirror().on(modelProject).invoke().method("getViews").withoutArgs();
for (final Object view : views) {
final List<Object> displayConnectors = (List<Object>) new Mirror().on(view).invoke().method("getDisplayConnectors").withoutArgs();
final String viewName = (String) new Mirror().on(view).invoke().method("getName").withoutArgs();
if (!this.displayMethods.containsKey(viewName)) {
this.displayMethods.put(viewName, new HashMap<String, Method>());
}
if (!this.displayObjects.containsKey(viewName)) {
this.displayObjects.put(viewName, new HashMap<String, Object>());
}
if (!this.displayPlugins.containsKey(viewName)) {
this.displayPlugins.put(viewName, new HashMap<String, Object>());
}
final Map<String, Method> methodMap = this.displayMethods.get(viewName);
final Map<String, Object> displayObjectMap = this.displayObjects.get(viewName);
final Map<String, Object> displayPluginMap = this.displayPlugins.get(viewName);
for (final Object displayConnector : displayConnectors) {
final Object display = new Mirror().on(displayConnector).invoke().method("getDisplay").withoutArgs();
final String displayName = (String) new Mirror().on(display).invoke().method("getName").withoutArgs();
final Object displayParent = new Mirror().on(display).invoke().method("getParent").withoutArgs();
final Object plugin = this.pluginMap.get(displayParent);
final Method[] methods = plugin.getClass().getMethods();
for (final Method method : methods) {
final Annotation displayAnnoation = method.getAnnotation(this.classAndMethodContainer.getDisplayAnnotationClass());
if (displayAnnoation != null) {
final String potentialDisplayName = (String) new Mirror().on(displayAnnoation).invoke().method("name").withoutArgs();
if (displayName.equals(potentialDisplayName)) {
methodMap.put(displayName, method);
displayObjectMap.put(displayName, method.getParameterTypes()[0].newInstance());
displayPluginMap.put(displayName, plugin);
break;
}
}
}
}
}
} catch (final ProjectLoadException ex) {
throw new AnalysisInitializationException("An error occured while instantiating the analysis.", ex);
} catch (final NullPointerException ex) {
throw new AnalysisInitializationException("An error occured while instantiating the analysis.", ex);
} catch (final IllegalAccessException ex) {
throw new AnalysisInitializationException("An error occured while instantiating the analysis.", ex);
} catch (final InstantiationException ex) {
throw new AnalysisInitializationException("An error occured while instantiating the analysis.", ex);
} catch (final MirrorException ex) {
throw new AnalysisInitializationException("An error occured while instantiating the analysis.", ex);
}
}
/**
......@@ -136,8 +188,7 @@ public class Analysis {
* @return A display object for the given parameters.
*/
public Object getDisplay(final String viewName, final String displayName) {
// return this.updateDisplaysThread.getDisplay(viewName, displayName);
return null;
return this.displayObjects.get(viewName).get(displayName);
}
/**
......@@ -150,7 +201,15 @@ public class Analysis {
AnalysisController.class.getName());
}
// public void updateDisplays() {
public void updateDisplays() {
for (final String view : this.displayMethods.keySet()) {
for (final String display : this.displayMethods.get(view).keySet()) {
final Object obj = this.displayObjects.get(view).get(display);
final Method method = this.displayMethods.get(view).get(display);
final Object plugin = this.displayPlugins.get(view).get(display);
// }
new Mirror().on(plugin).invoke().method(method).withArgs(obj);
}
}
}
}
......@@ -148,67 +148,72 @@ public class CurrentAnalysisEditorBean {
* their connections.
*/
public void initializeGraph() {
// Make sure that the currentAnalysisEditorGraphBean knows "this" as well.
this.currentAnalysisEditorGraphBean.setCurrentAnalysisEditorBean(this);
try {
// Make sure that the currentAnalysisEditorGraphBean knows "this" as well.
this.currentAnalysisEditorGraphBean.setCurrentAnalysisEditorBean(this);
// Initialize the graph
this.currentAnalysisEditorGraphBean.declareGraph();
// Initialize the graph
this.currentAnalysisEditorGraphBean.declareGraph();
// Initialize the component for the project configuration
this.initializeGlobalConfigurationInstance();
this.currentAnalysisEditorGraphBean.addGlobalConfigurationInstance(this.globalConfigurationInstance);
// Initialize the component for the project configuration
this.initializeGlobalConfigurationInstance();
this.currentAnalysisEditorGraphBean.addGlobalConfigurationInstance(this.globalConfigurationInstance);
// Initialize the reader, filter and repositories
for (final MIPlugin plugin : this.project.getPlugins()) {
if (plugin instanceof MIReader) {
this.currentAnalysisEditorGraphBean.addReader((MIReader) plugin);
} else {
this.currentAnalysisEditorGraphBean.addFilter((MIFilter) plugin);
// Initialize the reader, filter and repositories
for (final MIPlugin plugin : this.project.getPlugins()) {
if (plugin instanceof MIReader) {
this.currentAnalysisEditorGraphBean.addReader((MIReader) plugin);
} else {
this.currentAnalysisEditorGraphBean.addFilter((MIFilter) plugin);
}
}
}
for (final MIRepository repository : this.project.getRepositories()) {
this.currentAnalysisEditorGraphBean.addRepository(repository);
}
for (final MIRepository repository : this.project.getRepositories()) {
this.currentAnalysisEditorGraphBean.addRepository(repository);
}
// Now initialize the connections between filters...
for (final MIPlugin plugin : this.project.getPlugins()) {
for (final MIOutputPort oPort : plugin.getOutputPorts()) {
for (final MIInputPort iPort : oPort.getSubscribers()) {
this.currentAnalysisEditorGraphBean.addConnection(plugin, iPort.getParent(), oPort, iPort);
// Now initialize the connections between filters...
for (final MIPlugin plugin : this.project.getPlugins()) {
for (final MIOutputPort oPort : plugin.getOutputPorts()) {
for (final MIInputPort iPort : oPort.getSubscribers()) {
this.currentAnalysisEditorGraphBean.addConnection(plugin, iPort.getParent(), oPort, iPort);
}
}
}
}
// ...and between filters and repositories
for (final MIPlugin plugin : this.project.getPlugins()) {
for (final MIRepositoryConnector rPort : plugin.getRepositories()) {
// It is possible that the connected repository is null, if it hasn't been set yet. Check this.
if (rPort.getRepository() != null) {
this.currentAnalysisEditorGraphBean.addConnection(plugin, rPort.getRepository(), rPort);
// ...and between filters and repositories
for (final MIPlugin plugin : this.project.getPlugins()) {
for (final MIRepositoryConnector rPort : plugin.getRepositories()) {
// It is possible that the connected repository is null, if it hasn't been set yet. Check this.
if (rPort.getRepository() != null) {
this.currentAnalysisEditorGraphBean.addConnection(plugin, rPort.getRepository(), rPort);
}
}
}
}
// Initialize the mouse click and the edge constraint listeners
this.currentAnalysisEditorGraphBean.initListeners();
this.currentAnalysisEditorGraphBean.initEdgeConstraints();
// Initialize the mouse click and the edge constraint listeners
this.currentAnalysisEditorGraphBean.initListeners();
this.currentAnalysisEditorGraphBean.initEdgeConstraints();
// Now we have to set the default grid size and color of the user
this.currentAnalysisEditorGraphBean.setGridColor(this.userBean.getGridColor());
this.currentAnalysisEditorGraphBean.setGridSize(this.userBean.getGridSize());
// Now we have to set the default grid size and color of the user
this.currentAnalysisEditorGraphBean.setGridColor(this.userBean.getGridColor());
this.currentAnalysisEditorGraphBean.setGridSize(this.userBean.getGridSize());
// Perform either an initial auto layout or use the saved layout - if it exists
final String layout = this.projectService.getAnalysisLayout(this.projectName);
// Perform either an initial auto layout or use the saved layout - if it exists
final String layout = this.projectService.getAnalysisLayout(this.projectName);
if (layout != null) {
this.currentAnalysisEditorGraphBean.loadLayout(layout);
} else {
this.currentAnalysisEditorGraphBean.startAutoLayout();
}
if (layout != null) {
this.currentAnalysisEditorGraphBean.loadLayout(layout);
} else {
this.currentAnalysisEditorGraphBean.startAutoLayout();
}
// Make sure that guests cannot modify the graph
this.currentAnalysisEditorGraphBean.checkReadOnlyForGuests();
// Make sure that guests cannot modify the graph
this.currentAnalysisEditorGraphBean.checkReadOnlyForGuests();
} catch (final NullPointerException ex) {
// This exception can occur when a property has not been initialized
CurrentAnalysisEditorBean.LOG.error("An error occured while initializing the graph.", ex);
}
}
/**
......
......@@ -28,7 +28,6 @@ import kieker.analysis.model.analysisMetaModel.MIProject;
import kieker.analysis.model.analysisMetaModel.MIView;
import kieker.common.logging.Log;
import kieker.common.logging.LogFactory;
import kieker.webgui.common.ClassAndMethodContainer;
import kieker.webgui.common.exception.DisplayNotFoundException;
import kieker.webgui.common.exception.ProjectNotExistingException;
import kieker.webgui.service.IProjectService;
......@@ -42,6 +41,9 @@ import org.primefaces.model.DashboardModel;
import org.primefaces.model.DefaultDashboardColumn;
import org.primefaces.model.DefaultDashboardModel;
import net.vidageek.mirror.dsl.Mirror;
import net.vidageek.mirror.exception.MirrorException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
......@@ -65,7 +67,6 @@ public class CurrentCockpitBean {
private String projectName;
private MIProject project;
private MIView activeView;
private ClassAndMethodContainer classAndMethodContainer;
private Dashboard dashboard;
private DashboardModel dashboardModel;
......@@ -200,11 +201,12 @@ public class CurrentCockpitBean {
if ((this.activeView != null) && (this.projectName != null)) {
try {
final Object displayObj = this.projectService.getDisplay(this.projectName, this.activeView.getName(), displayName);
final String result = (String) ClassAndMethodContainer.invokeMethod(this.classAndMethodContainer.getPlainTextgetTextMethod(), displayObj,
"Error");
final String result = (String) new Mirror().on(displayObj).invoke().method("getText").withoutArgs();
return result;
} catch (final DisplayNotFoundException ex) {
CurrentCockpitBean.LOG.warn("Display not found.", ex);
} catch (final MirrorException ex) {
return "N/A";
}
}
return "N/A";
......
#------------------------------------------------------------------------------
#
# The following properties set the logging levels and log appender. The
# log4j.rootCategory variable defines the default log level and one or more
# appenders. For the console, use 'S'. For the daily rolling file, use 'R'.
# For an HTML formatted log, use 'H'.
#
# To override the default (rootCategory) log level, define a property of the
# form (see below for available values):
#
# log4j.logger. =
#
# Available logger names:
# TODO
#
# Possible Log Levels:
# FATAL, ERROR, WARN, INFO, DEBUG
#
#------------------------------------------------------------------------------
log4j.rootCategory=WARN, S
log4j.logger.com.dappit.Dapper.parser=ERROR
log4j.logger.org.w3c.tidy=FATAL
#------------------------------------------------------------------------------
#
# The following properties configure the console (stdout) appender.
# See http://logging.apache.org/log4j/docs/api/index.html for details.
#
#------------------------------------------------------------------------------
log4j.appender.S = org.apache.log4j.ConsoleAppender
log4j.appender.S.layout = org.apache.log4j.PatternLayout
log4j.appender.S.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} %c{1} [%p] %m%n
#------------------------------------------------------------------------------
#
# The following properties configure the Daily Rolling File appender.
# See http://logging.apache.org/log4j/docs/api/index.html for details.
# The following properties set the logging levels and log appender.
#
#------------------------------------------------------------------------------
log4j.appender.R = org.apache.log4j.DailyRollingFileAppender
log4j.appender.R.File = logs/bensApps.log
log4j.appender.R.Append = true
log4j.appender.R.DatePattern = '.'yyy-MM-dd
log4j.appender.R.layout = org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} %c{1} [%p] %m%n
log4j.rootCategory = WARN, file
#------------------------------------------------------------------------------
#
# The following properties configure the Rolling File appender in HTML.
# See http://logging.apache.org/log4j/docs/api/index.html for details.
# The following properties configure the message loffing to a log file
#
#------------------------------------------------------------------------------
log4j.appender.H = org.apache.log4j.RollingFileAppender
log4j.appender.H.File = logs/bensApps.html
log4j.appender.H.MaxFileSize = 100KB
log4j.appender.H.Append = false
log4j.appender.H.layout = org.apache.log4j.HTMLLayout
\ No newline at end of file
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File = logs/Kieker.WebGUI.log
log4j.appender.file.MaxFileSize = 10MB
log4j.appender.file.MaxBackupIndex = 1
log4j.appender.file.layout = org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
\ No newline at end of file
......@@ -43,14 +43,14 @@
<ui:define name="centerContent">
<p:tabView id="tabView" style="height: 99%" >
<p:tab title="#{localizedControllerPageMessages.personalLog}" style="height: 100%" >
<p:tab title="#{localizedControllerPageMessages.personalLog}" >
<h:form id="currentViewLog">
<ui:repeat value="#{currentControllerBean.viewLog}" var="entry" varStatus="stat">
<h:outputText value="#{entry}"/><ui:fragment rendered="#{not stat.last}"><br/><br/></ui:fragment>
</ui:repeat>
</h:form>
</p:tab>
<p:tab title="#{localizedControllerPageMessages.analysisControllerLog}" style="height: 100%">
<p:tab title="#{localizedControllerPageMessages.analysisControllerLog}">
<h:form id="analysisControllerLog">
<ui:repeat value="#{currentControllerBean.analysisLog}" var="entry" varStatus="stat">
<h:outputText value="#{entry}"/><ui:fragment rendered="#{not stat.last}"><br/><br/></ui:fragment>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment