summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Rudyy <orudyy@apache.org>2013-08-02 17:03:34 +0000
committerAlex Rudyy <orudyy@apache.org>2013-08-02 17:03:34 +0000
commit6968f5202decb4c2ecacab61c00552b0515cc805 (patch)
treec796e7359daec0129ed04dc7f7012ce471870b2a
parent9963e6fed203515ab3be76fa0e7eebeb63f78583 (diff)
downloadqpid-python-6968f5202decb4c2ecacab61c00552b0515cc805.tar.gz
QPID-5037: Move log viewer into a separate tab and add abilities to download logs and filter log entries in the logs table
git-svn-id: https://svn.apache.org/repos/asf/qpid/trunk/qpid@1509778 13f79535-47bb-0310-9956-ffa450edef68
-rw-r--r--java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java5
-rw-r--r--java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/log/LogFileDetails.java78
-rw-r--r--java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/log/LogFileHelper.java228
-rw-r--r--java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/LogFileServlet.java105
-rw-r--r--java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/LogFileListingServlet.java68
-rw-r--r--java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/LogRecordsServlet.java25
-rw-r--r--java/broker-plugins/management-http/src/main/java/resources/css/common.css58
-rw-r--r--java/broker-plugins/management-http/src/main/java/resources/grid/showColumnDefDialog.html32
-rw-r--r--java/broker-plugins/management-http/src/main/java/resources/grid/showRowNumberLimitDialog.html33
-rw-r--r--java/broker-plugins/management-http/src/main/java/resources/images/auto-refresh.pngbin0 -> 535 bytes
-rw-r--r--java/broker-plugins/management-http/src/main/java/resources/images/download.pngbin0 -> 803 bytes
-rw-r--r--java/broker-plugins/management-http/src/main/java/resources/images/log-viewer.pngbin0 -> 719 bytes
-rw-r--r--java/broker-plugins/management-http/src/main/java/resources/images/refresh.pngbin0 -> 468 bytes
-rw-r--r--java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/UpdatableStore.js73
-rw-r--r--java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/ColumnDefDialog.js140
-rw-r--r--java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/EnhancedFilter.js229
-rw-r--r--java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/EnhancedFilterTools.js270
-rw-r--r--java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/FilterSummary.js173
-rw-r--r--java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/GridUpdater.js244
-rw-r--r--java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/RowNumberLimitDialog.js96
-rw-r--r--java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/util.js16
-rw-r--r--java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/Broker.js52
-rw-r--r--java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/controller.js5
-rw-r--r--java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/logs/LogFileDownloadDialog.js175
-rw-r--r--java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/logs/LogViewer.js202
-rw-r--r--java/broker-plugins/management-http/src/main/java/resources/logs/showLogFileDownloadDialog.html33
-rw-r--r--java/broker-plugins/management-http/src/main/java/resources/logs/showLogViewer.html29
-rw-r--r--java/broker-plugins/management-http/src/main/java/resources/showBroker.html6
-rw-r--r--java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/log/LogFileHelperTest.java339
-rw-r--r--java/broker/etc/broker_example.acl3
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/configuration/BrokerProperties.java1
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/logging/LogRecorder.java4
-rwxr-xr-xjava/broker/src/main/java/org/apache/qpid/server/security/SecurityManager.java12
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/security/access/ObjectType.java3
-rw-r--r--java/broker/src/main/java/org/apache/qpid/server/security/access/Operation.java3
-rw-r--r--java/systests/src/main/java/org/apache/qpid/systest/rest/LogRecordsRestTest.java21
-rw-r--r--java/systests/src/main/java/org/apache/qpid/systest/rest/LogViewerTest.java105
-rw-r--r--java/systests/src/main/java/org/apache/qpid/systest/rest/RestTestHelper.java7
-rw-r--r--java/systests/src/main/java/org/apache/qpid/systest/rest/acl/LogViewerACLTest.java100
39 files changed, 2911 insertions, 62 deletions
diff --git a/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java
index d87a1755da..e66680ce12 100644
--- a/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java
+++ b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java
@@ -20,7 +20,6 @@
*/
package org.apache.qpid.server.management.plugin;
-import java.io.File;
import java.lang.reflect.Type;
import java.net.SocketAddress;
import java.util.Collection;
@@ -39,7 +38,9 @@ import org.apache.qpid.server.management.plugin.filter.ForbiddingAuthorisationFi
import org.apache.qpid.server.management.plugin.filter.RedirectingAuthorisationFilter;
import org.apache.qpid.server.management.plugin.servlet.DefinedFileServlet;
import org.apache.qpid.server.management.plugin.servlet.FileServlet;
+import org.apache.qpid.server.management.plugin.servlet.LogFileServlet;
import org.apache.qpid.server.management.plugin.servlet.rest.HelperServlet;
+import org.apache.qpid.server.management.plugin.servlet.rest.LogFileListingServlet;
import org.apache.qpid.server.management.plugin.servlet.rest.LogRecordsServlet;
import org.apache.qpid.server.management.plugin.servlet.rest.LogoutServlet;
import org.apache.qpid.server.management.plugin.servlet.rest.MessageContentServlet;
@@ -312,6 +313,8 @@ public class HttpManagement extends AbstractPluginAdapter implements HttpManagem
root.addServlet(new ServletHolder(FileServlet.INSTANCE), "*.txt");
root.addServlet(new ServletHolder(FileServlet.INSTANCE), "*.xsl");
root.addServlet(new ServletHolder(new HelperServlet()), "/rest/helper");
+ root.addServlet(new ServletHolder(new LogFileListingServlet()), "/rest/logfiles");
+ root.addServlet(new ServletHolder(new LogFileServlet()), "/rest/logfile");
final SessionManager sessionManager = root.getSessionHandler().getSessionManager();
sessionManager.setSessionCookie(JSESSIONID_COOKIE_PREFIX + lastPort);
diff --git a/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/log/LogFileDetails.java b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/log/LogFileDetails.java
new file mode 100644
index 0000000000..09dabd0e73
--- /dev/null
+++ b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/log/LogFileDetails.java
@@ -0,0 +1,78 @@
+/*
+ * 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.qpid.server.management.plugin.log;
+
+import java.io.File;
+
+public class LogFileDetails
+{
+ private String _name;
+ private File _location;
+ private String _mimeType;
+ private long _size;
+ private long _lastModified;
+ private String _appenderName;
+
+ public LogFileDetails(String name, String appenderName, File location, String mimeType, long fileSize, long lastUpdateTime)
+ {
+ super();
+ _name = name;
+ _location = location;
+ _mimeType = mimeType;
+ _size = fileSize;
+ _lastModified = lastUpdateTime;
+ _appenderName = appenderName;
+ }
+
+ public String getName()
+ {
+ return _name;
+ }
+
+ public File getLocation()
+ {
+ return _location;
+ }
+
+ public String getMimeType()
+ {
+ return _mimeType;
+ }
+
+ public long getSize()
+ {
+ return _size;
+ }
+
+ public long getLastModified()
+ {
+ return _lastModified;
+ }
+
+ public String getAppenderName()
+ {
+ return _appenderName;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "LogFileDetails [name=" + _name + "]";
+ }
+
+}
diff --git a/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/log/LogFileHelper.java b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/log/LogFileHelper.java
new file mode 100644
index 0000000000..03d98d020b
--- /dev/null
+++ b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/log/LogFileHelper.java
@@ -0,0 +1,228 @@
+/*
+ * 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.qpid.server.management.plugin.log;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.FileAppender;
+import org.apache.log4j.QpidCompositeRollingAppender;
+
+public class LogFileHelper
+{
+ public static final String GZIP_MIME_TYPE = "application/x-gzip";
+ public static final String TEXT_MIME_TYPE = "text/plain";
+ public static final String ZIP_MIME_TYPE = "application/zip";
+ public static final String GZIP_EXTENSION = ".gz";
+ private static final int BUFFER_LENGTH = 1024 * 4;
+ private Collection<Appender> _appenders;
+
+ public LogFileHelper(Collection<Appender> appenders)
+ {
+ super();
+ _appenders = appenders;
+ }
+
+ public List<LogFileDetails> findLogFileDetails(String[] requestedFiles)
+ {
+ List<LogFileDetails> logFiles = new ArrayList<LogFileDetails>();
+ Map<String, List<LogFileDetails>> cache = new HashMap<String, List<LogFileDetails>>();
+ for (int i = 0; i < requestedFiles.length; i++)
+ {
+ String[] paths = requestedFiles[i].split("/");
+ if (paths.length != 2)
+ {
+ throw new IllegalArgumentException("Log file name '" + requestedFiles[i] + "' does not include an appender name");
+ }
+
+ String appenderName = paths[0];
+ String fileName = paths[1];
+
+ List<LogFileDetails> appenderFiles = cache.get(appenderName);
+ if (appenderFiles == null)
+ {
+ Appender fileAppender = null;
+ for (Appender appender : _appenders)
+ {
+ if (appenderName.equals(appender.getName()))
+ {
+ fileAppender = appender;
+ break;
+ }
+ }
+ if (fileAppender == null)
+ {
+ continue;
+ }
+ appenderFiles = getAppenderFiles(fileAppender, true);
+ if (appenderFiles == null)
+ {
+ continue;
+ }
+ cache.put(appenderName, appenderFiles);
+ }
+ for (LogFileDetails logFileDetails : appenderFiles)
+ {
+ if (logFileDetails.getName().equals(fileName))
+ {
+ logFiles.add(logFileDetails);
+ }
+ }
+ }
+ return logFiles;
+ }
+
+ public List<LogFileDetails> getLogFileDetails(boolean includeLogFileLocation)
+ {
+ List<LogFileDetails> results = new ArrayList<LogFileDetails>();
+ for (Appender appender : _appenders)
+ {
+ List<LogFileDetails> appenderFiles = getAppenderFiles(appender, includeLogFileLocation);
+ if (appenderFiles != null)
+ {
+ results.addAll(appenderFiles);
+ }
+ }
+ return results;
+ }
+
+ public void writeLogFiles(List<LogFileDetails> logFiles, OutputStream os) throws IOException
+ {
+ ZipOutputStream out = new ZipOutputStream(os);
+ try
+ {
+ addLogFileEntries(logFiles, out);
+ }
+ finally
+ {
+ out.close();
+ }
+ }
+
+ public void writeLogFile(File file, OutputStream os) throws IOException
+ {
+ FileInputStream fis = new FileInputStream(file);
+ try
+ {
+ byte[] bytes = new byte[BUFFER_LENGTH];
+ int length = 1;
+ while ((length = fis.read(bytes)) != -1)
+ {
+ os.write(bytes, 0, length);
+ }
+ }
+ finally
+ {
+ fis.close();
+ }
+ }
+
+ private List<LogFileDetails> getAppenderFiles(Appender appender, boolean includeLogFileLocation)
+ {
+ if (appender instanceof QpidCompositeRollingAppender)
+ {
+ return listAppenderFiles((QpidCompositeRollingAppender) appender, includeLogFileLocation);
+ }
+ else if (appender instanceof FileAppender)
+ {
+ return listAppenderFiles((FileAppender) appender, includeLogFileLocation);
+ }
+ return null;
+ }
+
+ private List<LogFileDetails> listAppenderFiles(FileAppender appender, boolean includeLogFileLocation)
+ {
+ String appenderFilePath = appender.getFile();
+ File appenderFile = new File(appenderFilePath);
+ if (appenderFile.exists())
+ {
+ return listLogFiles(appenderFile.getParentFile(), appenderFile.getName(), appender.getName(), "", includeLogFileLocation);
+ }
+ return Collections.emptyList();
+ }
+
+ private List<LogFileDetails> listAppenderFiles(QpidCompositeRollingAppender appender, boolean includeLogFileLocation)
+ {
+ List<LogFileDetails> files = listAppenderFiles((FileAppender) appender, includeLogFileLocation);
+ String appenderFilePath = appender.getFile();
+ File appenderFile = new File(appenderFilePath);
+ File backupFolder = new File(appender.getBackupFilesToPath());
+ if (backupFolder.exists())
+ {
+ String backFolderName = backupFolder.getName() + "/";
+ List<LogFileDetails> backedUpFiles = listLogFiles(backupFolder, appenderFile.getName(), appender.getName(),
+ backFolderName, includeLogFileLocation);
+ files.addAll(backedUpFiles);
+ }
+ return files;
+ }
+
+ private List<LogFileDetails> listLogFiles(File parent, String baseFileName, String appenderName, String relativePath,
+ boolean includeLogFileLocation)
+ {
+ List<LogFileDetails> files = new ArrayList<LogFileDetails>();
+ for (File file : parent.listFiles())
+ {
+ String name = file.getName();
+ if (name.startsWith(baseFileName))
+ {
+ files.add(new LogFileDetails(name, appenderName, includeLogFileLocation ? file : null, getMimeType(name), file.length(),
+ file.lastModified()));
+ }
+ }
+ return files;
+ }
+
+ private String getMimeType(String fileName)
+ {
+ if (fileName.endsWith(GZIP_EXTENSION))
+ {
+ return GZIP_MIME_TYPE;
+ }
+ return TEXT_MIME_TYPE;
+ }
+
+ private void addLogFileEntries(List<LogFileDetails> files, ZipOutputStream out) throws IOException
+ {
+ for (LogFileDetails logFileDetails : files)
+ {
+ File file = logFileDetails.getLocation();
+ if (file.exists())
+ {
+ ZipEntry entry = new ZipEntry(logFileDetails.getAppenderName() + "/" + logFileDetails.getName());
+ entry.setSize(file.length());
+ out.putNextEntry(entry);
+ writeLogFile(file, out);
+ out.closeEntry();
+ }
+ out.flush();
+ }
+ }
+
+}
diff --git a/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/LogFileServlet.java b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/LogFileServlet.java
new file mode 100644
index 0000000000..1fa03dc3dc
--- /dev/null
+++ b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/LogFileServlet.java
@@ -0,0 +1,105 @@
+/*
+ * 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.qpid.server.management.plugin.servlet;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.text.SimpleDateFormat;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.log4j.LogManager;
+import org.apache.qpid.server.management.plugin.log.LogFileDetails;
+import org.apache.qpid.server.management.plugin.log.LogFileHelper;
+import org.apache.qpid.server.management.plugin.servlet.rest.AbstractServlet;
+
+public class LogFileServlet extends AbstractServlet
+{
+ private static final long serialVersionUID = 1L;
+
+ public static final String LOGS_FILE_NAME = "qpid-logs-%s.zip";
+ public static final String DATE_FORMAT = "yyyy-MM-dd-mmHHss";
+
+ @SuppressWarnings("unchecked")
+ private LogFileHelper _helper = new LogFileHelper(Collections.list(LogManager.getRootLogger().getAllAppenders()));
+
+ @Override
+ protected void doGetWithSubjectAndActor(HttpServletRequest request, HttpServletResponse response) throws IOException,
+ ServletException
+ {
+ response.setHeader("Cache-Control", "no-cache");
+ response.setHeader("Pragma", "no-cache");
+ response.setDateHeader("Expires", 0);
+
+ if (!getBroker().getSecurityManager().authoriseLogsAccess())
+ {
+ response.sendError(HttpServletResponse.SC_FORBIDDEN, "Log files download is denied");
+ return;
+ }
+
+ String[] requestedFiles = request.getParameterValues("l");
+
+ if (requestedFiles == null || requestedFiles.length == 0)
+ {
+ response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+ return;
+ }
+
+ List<LogFileDetails> logFiles = null;
+
+ try
+ {
+ logFiles = _helper.findLogFileDetails(requestedFiles);
+ }
+ catch(IllegalArgumentException e)
+ {
+ response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+ return;
+ }
+
+ if (logFiles.size() == 0)
+ {
+ response.sendError(HttpServletResponse.SC_NOT_FOUND);
+ return;
+ }
+
+ String fileName = String.format(LOGS_FILE_NAME, new SimpleDateFormat(DATE_FORMAT).format(new Date()));
+ response.setStatus(HttpServletResponse.SC_OK);
+ response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
+ response.setContentType(LogFileHelper.ZIP_MIME_TYPE);
+
+ OutputStream os = response.getOutputStream();
+ try
+ {
+ _helper.writeLogFiles(logFiles, os);
+ }
+ finally
+ {
+ if (os != null)
+ {
+ os.close();
+ }
+ }
+ }
+
+}
diff --git a/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/LogFileListingServlet.java b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/LogFileListingServlet.java
new file mode 100644
index 0000000000..b6face18e3
--- /dev/null
+++ b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/LogFileListingServlet.java
@@ -0,0 +1,68 @@
+/*
+ * 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.qpid.server.management.plugin.servlet.rest;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.List;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.log4j.LogManager;
+import org.apache.qpid.server.management.plugin.log.LogFileDetails;
+import org.apache.qpid.server.management.plugin.log.LogFileHelper;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.SerializationConfig;
+
+public class LogFileListingServlet extends AbstractServlet
+{
+ private static final long serialVersionUID = 1L;
+
+ @SuppressWarnings("unchecked")
+ private LogFileHelper _helper = new LogFileHelper(Collections.list(LogManager.getRootLogger().getAllAppenders()));
+
+ @Override
+ protected void doGetWithSubjectAndActor(HttpServletRequest request, HttpServletResponse response) throws IOException,
+ ServletException
+ {
+ response.setHeader("Cache-Control", "no-cache");
+ response.setHeader("Pragma", "no-cache");
+ response.setDateHeader("Expires", 0);
+
+ if (!getBroker().getSecurityManager().authoriseLogsAccess())
+ {
+ response.sendError(HttpServletResponse.SC_FORBIDDEN, "Log files download is denied");
+ return;
+ }
+
+ List<LogFileDetails> logFiles = _helper.getLogFileDetails(false);
+ response.setContentType("application/json");
+ response.setStatus(HttpServletResponse.SC_OK);
+
+ final PrintWriter writer = response.getWriter();
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true);
+ mapper.writeValue(writer, logFiles);
+
+ response.setStatus(HttpServletResponse.SC_OK);
+ }
+
+}
diff --git a/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/LogRecordsServlet.java b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/LogRecordsServlet.java
index f2cf5d7734..35523ddf0f 100644
--- a/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/LogRecordsServlet.java
+++ b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/LogRecordsServlet.java
@@ -31,6 +31,10 @@ import org.codehaus.jackson.map.SerializationConfig;
public class LogRecordsServlet extends AbstractServlet
{
+ private static final long serialVersionUID = 2L;
+
+ public static final String PARAM_LAST_LOG_ID = "lastLogId";
+
public LogRecordsServlet()
{
super();
@@ -46,12 +50,31 @@ public class LogRecordsServlet extends AbstractServlet
response.setHeader("Pragma","no-cache");
response.setDateHeader ("Expires", 0);
+ if (!getBroker().getSecurityManager().authoriseLogsAccess())
+ {
+ response.sendError(HttpServletResponse.SC_FORBIDDEN, "Broker logs access is denied");
+ return;
+ }
+
+ long lastLogId = 0;
+ try
+ {
+ lastLogId = Long.parseLong(request.getParameter(PARAM_LAST_LOG_ID));
+ }
+ catch(Exception e)
+ {
+ // ignore null and incorrect parameter values
+ }
+
List<Map<String,Object>> logRecords = new ArrayList<Map<String, Object>>();
LogRecorder logRecorder = getBroker().getLogRecorder();
for(LogRecorder.Record record : logRecorder)
{
- logRecords.add(logRecordToObject(record));
+ if (record.getId() > lastLogId)
+ {
+ logRecords.add(logRecordToObject(record));
+ }
}
final PrintWriter writer = response.getWriter();
diff --git a/java/broker-plugins/management-http/src/main/java/resources/css/common.css b/java/broker-plugins/management-http/src/main/java/resources/css/common.css
index 4c8b79ab82..e45c9cb463 100644
--- a/java/broker-plugins/management-http/src/main/java/resources/css/common.css
+++ b/java/broker-plugins/management-http/src/main/java/resources/css/common.css
@@ -92,4 +92,60 @@ div .messages {
.formLabel-labelCell {
font-weight: bold;
-} \ No newline at end of file
+}
+
+.columnDefDialogButtonIcon {
+ background: url("../dojo/dojox/grid/enhanced/resources/images/sprite_icons.png") no-repeat;
+ background-position: -260px 2px;
+ width: 14px;
+ height: 14px;
+}
+
+.logViewerIcon {
+ background: url("../images/log-viewer.png") no-repeat;
+ width: 14px;
+ height: 16px;
+}
+
+.downloadLogsIcon {
+ background: url("../images/download.png") no-repeat;
+ width: 14px;
+ height: 14px;
+}
+
+.dojoxGridFBarClearFilterButtontnIcon
+{
+ background: url("../dojo/dojox/grid/enhanced/resources/images/sprite_icons.png") no-repeat;
+ background-position: -120px -18px;
+ width: 14px;
+ height: 14px;
+}
+
+.rowNumberLimitIcon
+{
+ background: url("../dojo/dojox/grid/enhanced/resources/images/sprite_icons.png") no-repeat;
+ background-position: -240px -18px;
+ width: 14px;
+ height: 14px;
+}
+
+.gridRefreshIcon
+{
+ background: url("../images/refresh.png") no-repeat;
+ width: 16px;
+ height: 16px;
+}
+
+.gridAutoRefreshIcon
+{
+ background: url("../images/auto-refresh.png") no-repeat;
+ width: 16px;
+ height: 16px;
+}
+
+.redBackground tr{ background-color:#ffdcd7 !important; background-image: none !important;}
+.yellowBackground tr{background-color:#fbfddf !important; background-image: none !important;}
+.grayBackground tr{background-color:#eeeeee !important; background-image: none !important;}
+.dojoxGridRowOdd.grayBackground tr{ background-color:#e9e9e9 !important; background-image: none !important;}
+.dojoxGridRowOdd.yellowBackground tr{background-color:#fafdd5 !important; background-image: none !important;}
+.dojoxGridRowOdd.redBackground tr{background-color:#f4c1c1 !important; background-image: none !important;} \ No newline at end of file
diff --git a/java/broker-plugins/management-http/src/main/java/resources/grid/showColumnDefDialog.html b/java/broker-plugins/management-http/src/main/java/resources/grid/showColumnDefDialog.html
new file mode 100644
index 0000000000..5b6b8ad774
--- /dev/null
+++ b/java/broker-plugins/management-http/src/main/java/resources/grid/showColumnDefDialog.html
@@ -0,0 +1,32 @@
+<!--
+ -
+ - 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.
+ -
+ -->
+<div>
+ <div>
+ <div>Select columns to display:</div>
+ <div class="columnList"></div>
+ </div>
+ <div class="dijitDialogPaneActionBar">
+ <button value="Display" data-dojo-type="dijit.form.Button"
+ class="displayButton" data-dojo-props="label: 'Display' "></button>
+ <button value="Cancel" data-dojo-type="dijit.form.Button" data-dojo-props="label: 'Cancel'"
+ class="cancelButton"></button>
+ </div>
+</div>
diff --git a/java/broker-plugins/management-http/src/main/java/resources/grid/showRowNumberLimitDialog.html b/java/broker-plugins/management-http/src/main/java/resources/grid/showRowNumberLimitDialog.html
new file mode 100644
index 0000000000..087d54c0f9
--- /dev/null
+++ b/java/broker-plugins/management-http/src/main/java/resources/grid/showRowNumberLimitDialog.html
@@ -0,0 +1,33 @@
+<!--
+ -
+ - 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.
+ -
+ -->
+<div>
+ <div>
+ <div>Set the maximum number of rows to cache and display:</div>
+ <input class="rowNumberLimit" data-dojo-type="dijit.form.NumberSpinner"
+ data-dojo-props="invalidMessage: 'Invalid value', required: true, smallDelta: 1,mconstraints: {min:1,max:65535,places:0, pattern: '#####'}, label: 'Maximum number of rows:', name: 'rowNumberLimit'"></input>
+ </div>
+ <div class="dijitDialogPaneActionBar">
+ <button value="Submit" data-dojo-type="dijit.form.Button"
+ class="submitButton" data-dojo-props="label: 'Submit' "></button>
+ <button value="Cancel" data-dojo-type="dijit.form.Button" data-dojo-props="label: 'Cancel'"
+ class="cancelButton"></button>
+ </div>
+</div>
diff --git a/java/broker-plugins/management-http/src/main/java/resources/images/auto-refresh.png b/java/broker-plugins/management-http/src/main/java/resources/images/auto-refresh.png
new file mode 100644
index 0000000000..493636f467
--- /dev/null
+++ b/java/broker-plugins/management-http/src/main/java/resources/images/auto-refresh.png
Binary files differ
diff --git a/java/broker-plugins/management-http/src/main/java/resources/images/download.png b/java/broker-plugins/management-http/src/main/java/resources/images/download.png
new file mode 100644
index 0000000000..b64b41d476
--- /dev/null
+++ b/java/broker-plugins/management-http/src/main/java/resources/images/download.png
Binary files differ
diff --git a/java/broker-plugins/management-http/src/main/java/resources/images/log-viewer.png b/java/broker-plugins/management-http/src/main/java/resources/images/log-viewer.png
new file mode 100644
index 0000000000..858fd48beb
--- /dev/null
+++ b/java/broker-plugins/management-http/src/main/java/resources/images/log-viewer.png
Binary files differ
diff --git a/java/broker-plugins/management-http/src/main/java/resources/images/refresh.png b/java/broker-plugins/management-http/src/main/java/resources/images/refresh.png
new file mode 100644
index 0000000000..083044979b
--- /dev/null
+++ b/java/broker-plugins/management-http/src/main/java/resources/images/refresh.png
Binary files differ
diff --git a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/UpdatableStore.js b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/UpdatableStore.js
index f7ede1a7f7..ea3ba78372 100644
--- a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/UpdatableStore.js
+++ b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/UpdatableStore.js
@@ -23,12 +23,13 @@ define(["dojo/store/Memory",
"dojo/data/ObjectStore",
"dojo/store/Observable"], function (Memory, DataGrid, ObjectStore, Observable) {
- function UpdatableStore( data, divName, structure, func, props, Grid ) {
+ function UpdatableStore( data, divName, structure, func, props, Grid, notObservable ) {
var that = this;
var GridType = DataGrid;
- that.store = Observable(Memory({data: data, idProperty: "id"}));
+ that.memoryStore = new Memory({data: data, idProperty: "id"});
+ that.store = notObservable? that.memoryStore : new Observable(that.memoryStore);
that.dataStore = ObjectStore({objectStore: that.store});
var gridProperties = { store: that.dataStore,
@@ -63,7 +64,7 @@ define(["dojo/store/Memory",
UpdatableStore.prototype.update = function(data)
{
-
+ var changed = false;
var store = this.store;
var theItem;
@@ -78,7 +79,7 @@ define(["dojo/store/Memory",
}
}
store.remove(object.id);
-
+ changed = true;
});
// iterate over data...
@@ -91,20 +92,84 @@ define(["dojo/store/Memory",
if(theItem[ propName ] != data[i][ propName ]) {
theItem[ propName ] = data[i][ propName ];
modified = true;
+ changed = true;
}
}
}
if(modified) {
// ... check attributes for updates
store.notify(theItem, data[i].id);
+ changed = true;
}
} else {
// ,,, if not in the store then add
store.put(data[i]);
+ changed = true;
}
}
}
+ return changed;
+ };
+
+ function removeItemsFromArray(items, numberToRemove)
+ {
+ if (items)
+ {
+ if (numberToRemove > 0 && items.length > 0)
+ {
+ if (numberToRemove >= items.length)
+ {
+ numberToRemove = numberToRemove - items.length;
+ items.length = 0
+ }
+ else
+ {
+ items.splice(0, numberToRemove);
+ numberToRemove = 0;
+ }
+ }
+ }
+ return numberToRemove;
+ };
+
+ UpdatableStore.prototype.append = function(data, limit)
+ {
+ var changed = false;
+ var items = this.memoryStore.data;
+
+ if (limit)
+ {
+ var totalSize = items.length + (data ? data.length : 0);
+ var numberToRemove = totalSize - limit;
+
+ if (numberToRemove > 0)
+ {
+ changed = true;
+ numberToRemove = removeItemsFromArray(items, numberToRemove);
+ if (numberToRemove > 0)
+ {
+ removeItemsFromArray(data, numberToRemove);
+ }
+ }
+ }
+
+ if (data && data.length > 0)
+ {
+ changed = true;
+ items.push.apply(items, data);
+ }
+
+ this.memoryStore.setData(items);
+ return changed;
+ };
+
+ UpdatableStore.prototype.close = function()
+ {
+ this.dataStore.close();
+ this.dataStore = null;
+ this.store = null;
+ this.memoryStore = null;
};
return UpdatableStore;
});
diff --git a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/ColumnDefDialog.js b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/ColumnDefDialog.js
new file mode 100644
index 0000000000..d285dfaad6
--- /dev/null
+++ b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/ColumnDefDialog.js
@@ -0,0 +1,140 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+define([
+ "dojo/_base/declare",
+ "dojo/_base/event",
+ "dojo/_base/array",
+ "dojo/_base/lang",
+ "dojo/parser",
+ "dojo/dom-construct",
+ "dojo/query",
+ "dijit/registry",
+ "dijit/form/Button",
+ "dijit/form/CheckBox",
+ "dojox/grid/enhanced/plugins/Dialog",
+ "dojo/text!../../../grid/showColumnDefDialog.html",
+ "dojo/domReady!"
+], function(declare, event, array, lang, parser, dom, query, registry, Button, CheckBox, Dialog, template ){
+
+
+return declare("qpid.common.grid.ColumnDefDialog", null, {
+
+ grid: null,
+ containerNode: null,
+ _columns: [],
+ _dialog: null,
+
+ constructor: function(args){
+ var grid = this.grid = args.grid;
+
+ this.containerNode = dom.create("div", {innerHTML: template});
+ parser.parse(this.containerNode);
+
+ var submitButton = registry.byNode(query(".displayButton", this.containerNode)[0]);
+ this.closeButton = registry.byNode(query(".cancelButton", this.containerNode)[0]);
+ var columnsContainer = query(".columnList", this.containerNode)[0];
+
+ this._buildColumnWidgets(columnsContainer);
+
+ this._dialog = new Dialog({
+ "refNode": this.grid.domNode,
+ "title": "Grid Columns",
+ "content": this.containerNode
+ });
+
+ var self = this;
+ submitButton.on("click", function(e){self._onColumnsSelect(e); });
+ this.closeButton.on("click", function(e){self._dialog.hide(); });
+
+ this._dialog.startup();
+ },
+
+ destroy: function(){
+ this._dialog.destroyRecursive();
+ this._dialog = null;
+ this.grid = null;
+ this.containerNode = null;
+ this._columns = null;
+ },
+
+ showDialog: function(){
+ this._initColumnWidgets();
+ this._dialog.show();
+ },
+
+ _initColumnWidgets: function()
+ {
+ var cells = this.grid.layout.cells;
+ for(var i in cells)
+ {
+ var cell = cells[i];
+ this._columns[cell.name].checked = !cell.hidden;
+ }
+ },
+
+ _onColumnsSelect: function(evt){
+ event.stop(evt);
+ var grid = this.grid;
+ grid.beginUpdate();
+ var cells = grid.layout.cells;
+ try
+ {
+ for(var i in cells)
+ {
+ var cell = cells[i];
+ var widget = this._columns[cell.name];
+ grid.layout.setColumnVisibility(i, widget.checked);
+ }
+ }
+ finally
+ {
+ grid.endUpdate();
+ this._dialog.hide();
+ }
+ },
+
+ _buildColumnWidgets: function(columnsContainer)
+ {
+ var cells = this.grid.layout.cells;
+ for(var i in cells)
+ {
+ var cell = cells[i];
+ var widget = new dijit.form.CheckBox({
+ required: false,
+ checked: !cell.hidden,
+ label: cell.name,
+ name: this.grid.id + "_cchb_ " + i
+ });
+
+ this._columns[cell.name] = widget;
+
+ var div = dom.create("div");
+ div.appendChild(widget.domNode);
+ div.appendChild(dom.create("span", {innerHTML: cell.name}));
+
+ columnsContainer.appendChild(div);
+ }
+ }
+
+ });
+
+});
diff --git a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/EnhancedFilter.js b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/EnhancedFilter.js
new file mode 100644
index 0000000000..c882447f5d
--- /dev/null
+++ b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/EnhancedFilter.js
@@ -0,0 +1,229 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+define([
+ "dojo/_base/declare",
+ "dojo/_base/lang",
+ "dojo/_base/array",
+ "dijit/Toolbar",
+ "dojox/grid/enhanced/_Plugin",
+ "dojox/grid/enhanced/plugins/Dialog",
+ "dojox/grid/enhanced/plugins/filter/FilterLayer",
+ "dojox/grid/enhanced/plugins/filter/FilterDefDialog",
+ "dojox/grid/enhanced/plugins/filter/FilterStatusTip",
+ "dojox/grid/enhanced/plugins/filter/ClearFilterConfirm",
+ "dojox/grid/EnhancedGrid",
+ "dojo/i18n!dojox/grid/enhanced/nls/Filter",
+ "qpid/common/grid/EnhancedFilterTools"
+], function(declare, lang, array, Toolbar, _Plugin,
+ Dialog, FilterLayer, FilterDefDialog, FilterStatusTip, ClearFilterConfirm, EnhancedGrid, nls, EnhancedFilterTools){
+
+ // override CriteriaBox#_getColumnOptions to show criteria for hidden columns with EnhancedFilter
+ dojo.extend(dojox.grid.enhanced.plugins.filter.CriteriaBox, {
+ _getColumnOptions: function(){
+ var colIdx = this.dlg.curColIdx >= 0 ? String(this.dlg.curColIdx) : "anycolumn";
+ var filterHidden = this.plugin.filterHidden;
+ return array.map(array.filter(this.plugin.grid.layout.cells, function(cell){
+ return !(cell.filterable === false || (!filterHidden && cell.hidden));
+ }), function(cell){
+ return {
+ label: cell.name || cell.field,
+ value: String(cell.index),
+ selected: colIdx == String(cell.index)
+ };
+ });
+ }
+ });
+
+ // Enhanced filter has extra functionality for refreshing, limiting rows, displaying/hiding columns in the grid
+ var EnhancedFilter = declare("qpid.common.grid.EnhancedFilter", _Plugin, {
+ // summary:
+ // Accept the same plugin parameters as dojox.grid.enhanced.plugins.Filter and the following:
+ //
+ // filterHidden: boolean:
+ // Whether to display filtering criteria for hidden columns. Default to true.
+ //
+ // defaulGridRowLimit: int:
+ // Default limit for numbers of items to cache in the gris dtore
+ //
+ // disableFiltering: boolean:
+ // Whether to disable a filtering including filter button, clear filter button and filter summary.
+ //
+ // toolbar: dijit.Toolbar:
+ // An instance of toolbar to add the enhanced filter widgets.
+
+
+ // name: String
+ // plugin name
+ name: "enhancedFilter",
+
+ // filterHidden: Boolean
+ // whether to filter hidden columns
+ filterHidden: true,
+
+ constructor: function(grid, args){
+ // summary:
+ // See constructor of dojox.grid.enhanced._Plugin.
+ this.grid = grid;
+ this.nls = nls;
+
+ args = this.args = lang.isObject(args) ? args : {};
+ if(typeof args.ruleCount != 'number' || args.ruleCount < 0){
+ args.ruleCount = 0;
+ }
+ var rc = this.ruleCountToConfirmClearFilter = args.ruleCountToConfirmClearFilter;
+ if(rc === undefined){
+ this.ruleCountToConfirmClearFilter = 5;
+ }
+
+ if (args.filterHidden){
+ this.filterHidden = args.filterHidden;
+ }
+ this.defaulGridRowLimit = args.defaulGridRowLimit;
+ this.disableFiltering = args.disableFiltering;
+
+ //Install UI components
+ var obj = { "plugin": this };
+
+ this.filterBar = ( args.toolbar && args.toolbar instanceof dijit.Toolbar) ? args.toolbar: new Toolbar();
+
+ if (!this.disableFiltering)
+ {
+ //Install filter layer
+ this._wrapStore();
+
+ this.clearFilterDialog = new Dialog({
+ refNode: this.grid.domNode,
+ title: this.nls["clearFilterDialogTitle"],
+ content: new ClearFilterConfirm(obj)
+ });
+
+ this.filterDefDialog = new FilterDefDialog(obj);
+
+ nls["statusTipTitleNoFilter"] = "Filter is not set";
+ nls["statusTipMsg"] = "Click on 'Set Filter' button to specify filtering conditions";
+ this.filterStatusTip = new FilterStatusTip(obj);
+
+ var self = this;
+ var toggleClearFilterBtn = function (arg){ self.enhancedFilterTools.toggleClearFilterBtn(arg); };
+
+ this.filterBar.toggleClearFilterBtn = toggleClearFilterBtn;
+
+ this.grid.isFilterBarShown = function (){return true};
+
+ this.connect(this.grid.layer("filter"), "onFilterDefined", function(filter){
+ toggleClearFilterBtn(true);
+ });
+
+ //Expose the layer event to grid.
+ grid.onFilterDefined = function(){};
+ this.connect(grid.layer("filter"), "onFilterDefined", function(filter){
+ grid.onFilterDefined(grid.getFilter(), grid.getFilterRelation());
+ });
+ }
+
+ // add extra buttons into toolbar
+ this.enhancedFilterTools = new EnhancedFilterTools({
+ grid: grid,
+ toolbar: this.filterBar,
+ filterStatusTip: this.filterStatusTip,
+ clearFilterDialog: this.clearFilterDialog,
+ filterDefDialog: this.filterDefDialog,
+ defaulGridRowLimit: this.defaulGridRowLimit,
+ disableFiltering: this.disableFiltering,
+ nls: nls
+ });
+
+ this.filterBar.placeAt(this.grid.viewsHeaderNode, "before");
+ this.filterBar.startup();
+
+ },
+
+ destroy: function(){
+ this.inherited(arguments);
+ try
+ {
+ if (this.grid)
+ {
+ this.grid.unwrap("filter");
+ this.grid = null;
+ }
+ if (this.filterBar)
+ {
+ this.filterBar.destroyRecursive();
+ this.filterBar = null;
+ }
+ if (this.enhancedFilterTools)
+ {
+ this.enhancedFilterTools.destroy();
+ this.enhancedFilterTools = null;
+ }
+ if (this.clearFilterDialog)
+ {
+ this.clearFilterDialog.destroyRecursive();
+ this.clearFilterDialog = null;
+ }
+ if (this.filterStatusTip)
+ {
+ this.filterStatusTip.destroy();
+ this.filterStatusTip = null;
+ }
+ if (this.filterDefDialog)
+ {
+ this.filterDefDialog.destroy();
+ this.filterDefDialog = null;
+ }
+ this.args = null;
+
+ }catch(e){
+ console.warn("Filter.destroy() error:",e);
+ }
+ },
+
+ _wrapStore: function(){
+ var g = this.grid;
+ var args = this.args;
+ var filterLayer = args.isServerSide ? new FilterLayer.ServerSideFilterLayer(args) :
+ new FilterLayer.ClientSideFilterLayer({
+ cacheSize: args.filterCacheSize,
+ fetchAll: args.fetchAllOnFirstFilter,
+ getter: this._clientFilterGetter
+ });
+ FilterLayer.wrap(g, "_storeLayerFetch", filterLayer);
+
+ this.connect(g, "_onDelete", lang.hitch(filterLayer, "invalidate"));
+ },
+
+ onSetStore: function(store){
+ this.filterDefDialog.clearFilter(true);
+ },
+
+ _clientFilterGetter: function(/* data item */ datarow,/* cell */cell, /* int */rowIndex){
+ return cell.get(rowIndex, datarow);
+ }
+
+ });
+
+ EnhancedGrid.registerPlugin(EnhancedFilter);
+
+ return EnhancedFilter;
+
+});
diff --git a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/EnhancedFilterTools.js b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/EnhancedFilterTools.js
new file mode 100644
index 0000000000..b1645b4905
--- /dev/null
+++ b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/EnhancedFilterTools.js
@@ -0,0 +1,270 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+define([
+ "dojo/_base/declare",
+ "dojo/_base/event",
+ "dijit/form/Button",
+ "dijit/form/ToggleButton",
+ "qpid/common/grid/RowNumberLimitDialog",
+ "qpid/common/grid/ColumnDefDialog",
+ "qpid/common/grid/FilterSummary"
+], function(declare, event, Button, ToggleButton, RowNumberLimitDialog, ColumnDefDialog, FilterSummary){
+
+ var _stopEvent = function (evt){
+ try{
+ if(evt && evt.preventDefault){
+ event.stop(evt);
+ }
+ }catch(e){}
+ };
+
+ return declare("qpid.common.grid.EnhancedFilterTools", null, {
+
+ grid: null,
+ filterBar: null,
+ filterStatusTip: null,
+ clearFilterDialog: null,
+ filterDefDialog: null,
+
+ columnDefDialog: null,
+ columnDefButton: null,
+ filterDefButton: null,
+ clearFilterButton: null,
+ filterSummary: null,
+ setRowNumberLimitButton: null,
+ setRowNumberLimitDialog: null,
+ refreshButton: null,
+ autoRefreshButton: null,
+
+ constructor: function(params)
+ {
+ this.inherited(arguments);
+
+ this.filterBar = params.toolbar;
+ this.grid = params.grid;
+ this.filterStatusTip= params.filterStatusTip;
+ this.clearFilterDialog = params.clearFilterDialog;
+ this.filterDefDialog = params.filterDefDialog;
+
+ this._addRefreshButtons();
+ this._addRowLimitButton(params.defaulGridRowLimit);
+ this._addColumnsButton();
+
+ if (!params.disableFiltering)
+ {
+ this._addFilteringTools(params.nls);
+ }
+ },
+
+ toggleClearFilterBtn: function(clearFlag)
+ {
+ var filterLayer = this.grid.layer("filter");
+ var filterSet = filterLayer && filterLayer.filterDef && filterLayer.filterDef();
+ this.clearFilterButton.set("disabled", !filterSet);
+ },
+
+ destroy: function()
+ {
+ this.inherited(arguments);
+
+ if (this.columnDefDialog)
+ {
+ this.columnDefDialog.destroy();
+ this.columnDefDialog = null;
+ }
+ if (this.columnDefButton)
+ {
+ this.columnDefButton.destroy();
+ this.columnDefButton = null;
+ }
+ if (this.filterDefButton)
+ {
+ this.filterDefButton.destroy();
+ this.filterDefButton = null;
+ }
+ if (this.clearFilterButton)
+ {
+ this.clearFilterButton.destroy();
+ this.clearFilterButton = null;
+ }
+ if (this.filterSummary)
+ {
+ this.filterSummary.destroy();
+ this.filterSummary = null;
+ }
+ if (this.setRowNumberLimitButton)
+ {
+ this.setRowNumberLimitButton.destroy();
+ this.setRowNumberLimitButton = null;
+ }
+ if (this.setRowNumberLimitDialog)
+ {
+ this.setRowNumberLimitDialog.destroy();
+ this.setRowNumberLimitDialog = null;
+ }
+ if (this.refreshButton)
+ {
+ this.refreshButton.destroy();
+ this.refreshButton = null;
+ }
+ if (this.autoRefreshButton)
+ {
+ this.autoRefreshButton.destroy();
+ this.autoRefreshButton = null;
+ }
+
+ this.grid = null;
+ this.filterBar = null;
+ this.filterStatusTip = null;
+ this.clearFilterDialog = null;
+ this.filterDefDialog = null;
+ },
+
+ _addRefreshButtons: function()
+ {
+ var self = this;
+ this.refreshButton = new dijit.form.Button({
+ label: "Refresh",
+ type: "button",
+ iconClass: "gridRefreshIcon",
+ title: "Manual Refresh"
+ });
+
+ this.autoRefreshButton = new dijit.form.ToggleButton({
+ label: "Auto Refresh",
+ type: "button",
+ iconClass: "gridAutoRefreshIcon",
+ title: "Auto Refresh"
+ });
+
+ this.autoRefreshButton.on("change", function(value){
+ self.grid.updater.updatable=value;
+ self.refreshButton.set("disabled", value);
+ });
+
+ this.refreshButton.on("click", function(value){
+ self.grid.updater.performUpdate();
+ });
+
+ this.filterBar.addChild(this.autoRefreshButton);
+ this.filterBar.addChild(this.refreshButton);
+ },
+
+ _addRowLimitButton: function(defaulGridRowLimit)
+ {
+ var self = this;
+ this.setRowNumberLimitButton = new dijit.form.Button({
+ label: "Set Row Limit",
+ type: "button",
+ iconClass: "rowNumberLimitIcon",
+ title: "Set Row Number Limit"
+ });
+ this.setRowNumberLimitButton.set("title", "Set Row Number Limit (Current: " + defaulGridRowLimit +")");
+
+ this.setRowNumberLimitDialog = new RowNumberLimitDialog(this.grid.domNode, function(newLimit){
+ if (newLimit > 0 && self.grid.updater.appendLimit != newLimit )
+ {
+ self.grid.updater.appendLimit = newLimit;
+ self.grid.updater.performRefresh([]);
+ self.setRowNumberLimitButton.set("title", "Set Row Number Limit (Current: " + newLimit +")");
+ }
+ });
+
+ this.setRowNumberLimitButton.on("click", function(evt){
+ self.setRowNumberLimitDialog.showDialog(self.grid.updater.appendLimit);
+ });
+
+ this.filterBar.addChild(this.setRowNumberLimitButton);
+ },
+
+ _addColumnsButton: function()
+ {
+ var self = this;
+ this.columnDefDialog = new ColumnDefDialog({grid: this.grid});
+
+ this.columnDefButton = new dijit.form.Button({
+ label: "Display Columns",
+ type: "button",
+ iconClass: "columnDefDialogButtonIcon",
+ title: "Show/Hide Columns"
+ });
+
+ this.columnDefButton.on("click", function(e){
+ _stopEvent(e);
+ self.columnDefDialog.showDialog();
+ });
+
+ this.filterBar.addChild(this.columnDefButton);
+ },
+
+ _addFilteringTools: function(nls)
+ {
+ var self = this;
+
+ this.filterDefButton = new dijit.form.Button({
+ "class": "dojoxGridFBarBtn",
+ label: "Set Filter",
+ iconClass: "dojoxGridFBarDefFilterBtnIcon",
+ showLabel: "true",
+ title: "Define filter"
+ });
+
+ this.clearFilterButton = new dijit.form.Button({
+ "class": "dojoxGridFBarBtn",
+ label: "Clear filter",
+ iconClass: "dojoxGridFBarClearFilterButtontnIcon",
+ showLabel: "true",
+ title: "Clear filter",
+ disabled: true
+ });
+
+
+ this.filterDefButton.on("click", function(e){
+ _stopEvent(e);
+ self.filterDefDialog.showDialog();
+ });
+
+ this.clearFilterButton.on("click", function(e){
+ _stopEvent(e);
+ if (self.ruleCountToConfirmClearFilter && self.filterDefDialog.getCriteria() >= self.ruleCountToConfirmClearFilter)
+ {
+ self.clearFilterDialog.show();
+ }
+ else
+ {
+ self.grid.layer("filter").filterDef(null);
+ self.toggleClearFilterBtn(true)
+ }
+ });
+
+ this.filterSummary = new FilterSummary({grid: this.grid, filterStatusTip: this.filterStatusTip, nls: nls});
+
+ this.filterBar.addChild(this.filterDefButton);
+ this.filterBar.addChild(this.clearFilterButton);
+
+ this.filterBar.addChild(new dijit.ToolbarSeparator());
+ this.filterBar.addChild(this.filterSummary, "last");
+ this.filterBar.getColumnIdx = function(coordX){return self.filterSummary._getColumnIdx(coordX);};
+
+ }
+ });
+}); \ No newline at end of file
diff --git a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/FilterSummary.js b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/FilterSummary.js
new file mode 100644
index 0000000000..2b1d960fa5
--- /dev/null
+++ b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/FilterSummary.js
@@ -0,0 +1,173 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+define([
+ "dojo/_base/declare",
+ "dojo/_base/lang",
+ "dojo/_base/html",
+ "dojo/query",
+ "dojo/dom-construct",
+ "dojo/string",
+ "dojo/on",
+ "dijit/_WidgetBase"
+], function(declare, lang, html, query, domConstruct, string, on, _WidgetBase){
+
+return declare("qpid.common.grid.FilterSummary", [_WidgetBase], {
+
+ domNode: null,
+ itemName: null,
+ filterStatusTip: null,
+ grid: null,
+ _handle_statusTooltip: null,
+ _timeout_statusTooltip: 300,
+ _nls: null,
+
+ constructor: function(params)
+ {
+ this.inherited(arguments);
+ this.itemName = params.itemsName;
+ this.initialize(params.filterStatusTip, params.grid);
+ this._nls = params.nls;
+ },
+
+ buildRendering: function(){
+ this.inherited(arguments);
+ var itemsName = this.itemName || this._nls["defaultItemsName"];
+ var message = string.substitute(this._nls["filterBarMsgNoFilterTemplate"], [0, itemsName ]);
+ this.domNode = domConstruct.create("span", {innerHTML: message, "class": "dijit dijitReset dijitInline dijitButtonInline", role: "presentation" });
+ },
+
+ postCreate: function(){
+ this.inherited(arguments);
+ on(this.domNode, "mouseenter", lang.hitch(this, this._onMouseEnter));
+ on(this.domNode, "mouseleave", lang.hitch(this, this._onMouseLeave));
+ on(this.domNode, "mousemove", lang.hitch(this, this._onMouseMove));
+ },
+
+ destroy: function()
+ {
+ this.inherited(arguments);
+ this.itemName = null;
+ this.filterStatusTip = null;
+ this.grid = null;
+ this._handle_statusTooltip = null;
+ this._filteredClass = null;
+ this._nls = null;
+ },
+
+ initialize: function(filterStatusTip, grid)
+ {
+ this.filterStatusTip = filterStatusTip;
+ this.grid = grid;
+ if (this.grid)
+ {
+ var filterLayer = grid.layer("filter");
+ this.connect(filterLayer, "onFiltered", this.onFiltered);
+ }
+ },
+
+ onFiltered: function(filteredSize, originSize)
+ {
+ try
+ {
+ var itemsName = this.itemName || this._nls["defaultItemsName"],
+ msg = "", g = this.grid,
+ filterLayer = g.layer("filter");
+ if(filterLayer.filterDef()){
+ msg = string.substitute(this._nls["filterBarMsgHasFilterTemplate"], [filteredSize, originSize, itemsName]);
+ }else{
+ msg = string.substitute(this._nls["filterBarMsgNoFilterTemplate"], [originSize, itemsName]);
+ }
+ this.domNode.innerHTML = msg;
+ }
+ catch(e)
+ {
+ // swallow and log exception
+ // otherwise grid rendering is screwed
+ console.error(e);
+ }
+ },
+
+ _getColumnIdx: function(coordX){
+ var headers = query("[role='columnheader']", this.grid.viewsHeaderNode);
+ var idx = -1;
+ for(var i = headers.length - 1; i >= 0; --i){
+ var coord = html.position(headers[i]);
+ if(coordX >= coord.x && coordX < coord.x + coord.w){
+ idx = i;
+ break;
+ }
+ }
+ if(idx >= 0 && this.grid.layout.cells[idx].filterable !== false){
+ return idx;
+ }else{
+ return -1;
+ }
+ },
+
+ _setStatusTipTimeout: function(){
+ this._clearStatusTipTimeout();
+ this._handle_statusTooltip = setTimeout(lang.hitch(this,this._showStatusTooltip),this._timeout_statusTooltip);
+ },
+
+ _clearStatusTipTimeout: function(){
+ if (this._handle_statusTooltip){
+ clearTimeout(this._handle_statusTooltip);
+ }
+ this._handle_statusTooltip = null;
+ },
+
+ _showStatusTooltip: function(){
+ this._handle_statusTooltip = null;
+ if(this.filterStatusTip){
+ this.filterStatusTip.showDialog(this._tippos.x, this._tippos.y, this._getColumnIdx(this._tippos.x));
+ }
+ },
+
+ _updateTipPosition: function(evt){
+ this._tippos = {
+ x: evt.pageX,
+ y: evt.pageY
+ };
+ },
+
+ _onMouseEnter: function(e){
+ this._updateTipPosition(e);
+ if(this.filterStatusTip){
+ this._setStatusTipTimeout();
+ }
+ },
+
+ _onMouseMove: function(e){
+ if(this.filterStatusTip){
+ this._setStatusTipTimeout();
+ if(this._handle_statusTooltip){
+ this._updateTipPosition(e);
+ }
+ }
+ },
+
+ _onMouseLeave: function(e){
+ this._clearStatusTipTimeout();
+ },
+ });
+
+}); \ No newline at end of file
diff --git a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/GridUpdater.js b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/GridUpdater.js
new file mode 100644
index 0000000000..bb1e32bc31
--- /dev/null
+++ b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/GridUpdater.js
@@ -0,0 +1,244 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+
+define(["dojo/_base/xhr",
+ "dojo/parser",
+ "dojo/_base/array",
+ "dojo/_base/lang",
+ "qpid/common/properties",
+ "qpid/common/updater",
+ "qpid/common/UpdatableStore",
+ "qpid/common/util",
+ "dojox/grid/EnhancedGrid",
+ "qpid/common/grid/EnhancedFilter",
+ "dojox/grid/enhanced/plugins/NestedSorting",
+ "dojo/domReady!"],
+ function (xhr, parser, array, lang, properties, updater, UpdatableStore, util, EnhancedGrid, EnhancedFilter, NestedSorting) {
+
+ /*
+ * Construct GridUpdater from the following arguments:
+ * serviceUrl - service URL to fetch data for the grid. Optional, if data is specified
+ * data - array containing data for the grid. Optional, if serviceUrl is specified
+ * node - dom node or dom node id to
+ * structure,
+ * funct,
+ * gridProperties,
+ * gridConstructor
+ */
+ function GridUpdater(args) {
+
+ var self = this;
+
+ // GridUpdater fields
+ this.updatable = args.hasOwnProperty("updatable") ? args.updatable : true ;
+ this.serviceUrl = args.serviceUrl;
+ this.updatableStore = null;
+ this.grid = null;
+ this.onUpdate = args.onUpdate;
+ this._args = args;
+ this.appendData = args.append;
+ this.appendLimit = args.appendLimit;
+
+ // default grid properties
+ var gridProperties = {
+ autoHeight: true,
+ updateDelay: 0, // no delay updates when receiving notifications from a datastore
+ plugins: {
+ pagination: {
+ defaultPageSize: 25,
+ pageSizes: [10, 25, 50, 100],
+ description: true,
+ sizeSwitch: true,
+ pageStepper: true,
+ gotoButton: true,
+ maxPageStep: 4,
+ position: "bottom"
+ },
+ enhancedFilter: {}
+ }
+ };
+
+ var filterPluginFound = false;
+
+ // merge args grid properties with default grid properties
+ if(args && args.gridProperties)
+ {
+ var argProperties = args.gridProperties;
+ for(var argProperty in argProperties)
+ {
+ if(argProperties.hasOwnProperty(argProperty))
+ {
+ if (argProperty == "plugins")
+ {
+ var argPlugins = argProperties[ argProperty ];
+ for(var argPlugin in argPlugins)
+ {
+ if (argPlugin == "filter")
+ {
+ // we need to switch off filtering in EnhancedFilter
+ filterPluginFound = true;
+ }
+ if(argPlugins.hasOwnProperty(argPlugin))
+ {
+ var argPluginProperties = argPlugins[ argPlugin ];
+ if (argPluginProperties && gridProperties.plugins.hasOwnProperty(argPlugin))
+ {
+ var gridPlugin = gridProperties.plugins[ argPlugin ];
+ for(var pluginProperty in argPluginProperties)
+ {
+ if(argPluginProperties.hasOwnProperty(pluginProperty))
+ {
+ gridPlugin[pluginProperty] = argPluginProperties[pluginProperty];
+ }
+ }
+ }
+ else
+ {
+ gridProperties.plugins[ argPlugin ] = argPlugins[ argPlugin ];
+ }
+ }
+ }
+ }
+ else
+ {
+ gridProperties[ argProperty ] = argProperties[ argProperty ];
+ }
+ }
+ }
+ }
+
+ if (filterPluginFound)
+ {
+ gridProperties.plugins.enhancedFilter.disableFiltering = true;
+ }
+
+ var updatableStoreFactory = function(data)
+ {
+ try
+ {
+ self.updatableStore = new UpdatableStore(data, self._args.node, self._args.structure,
+ self._args.funct, gridProperties, self._args.GridConstructor || EnhancedGrid, self.appendData);
+ }
+ catch(e)
+ {
+ console.error(e);
+ throw e;
+ }
+ self.grid = self.updatableStore.grid;
+ self.grid.updater = self;
+ if (self.onUpdate)
+ {
+ self.onUpdate(data);
+ }
+ if (self.serviceUrl)
+ {
+ updater.add(self);
+ }
+ };
+
+ if (args && args.serviceUrl)
+ {
+ var requestUrl = lang.isFunction(this.serviceUrl) ? this.serviceUrl() : this.serviceUrl;
+ xhr.get({url: requestUrl, sync: properties.useSyncGet, handleAs: "json"}).then(updatableStoreFactory, util.errorHandler);
+ }
+ else if (args && args.data)
+ {
+ updatableStoreFactory(args.data);
+ }
+ }
+
+ GridUpdater.prototype.destroy = function()
+ {
+ updater.remove(this);
+ if (this.updatableStore)
+ {
+ this.updatableStore.close();
+ this.updatableStore = null;
+ }
+ if (this.grid)
+ {
+ this.grid.destroy();
+ this.grid = null;
+ }
+ };
+
+ GridUpdater.prototype.refresh = function(data)
+ {
+ this.updating = true;
+ try
+ {
+ if (this.appendData ? this.updatableStore.append(data, this.appendLimit): this.updatableStore.update(data))
+ {
+ // EnhancedGrid with Filter plugin has "filter" layer.
+ // The filter expression needs to be re-applied after the data update
+ var filterLayer = this.grid.layer("filter");
+ if ( filterLayer && filterLayer.filterDef)
+ {
+ var currentFilter = filterLayer.filterDef();
+
+ if (currentFilter)
+ {
+ // re-apply filter in the filter layer
+ filterLayer.filterDef(currentFilter);
+ }
+ }
+
+ // refresh grid to render updates
+ this.grid._refresh();
+ }
+ }
+ finally
+ {
+ this.updating = false;
+ if (this.onUpdate)
+ {
+ this.onUpdate(data);
+ }
+ }
+ }
+
+ GridUpdater.prototype.update = function()
+ {
+ if (this.updatable)
+ {
+ this.performUpdate();
+ }
+ };
+
+ GridUpdater.prototype.performUpdate = function()
+ {
+ var self = this;
+ var requestUrl = lang.isFunction(this.serviceUrl) ? this.serviceUrl() : this.serviceUrl;
+ var requestArguments = {url: requestUrl, sync: properties.useSyncGet, handleAs: "json"};
+ xhr.get(requestArguments).then(function(data){self.refresh(data);});
+ };
+
+ GridUpdater.prototype.performRefresh = function(data)
+ {
+ if (!this.updating)
+ {
+ this.refresh(data);
+ }
+ };
+
+ return GridUpdater;
+ });
diff --git a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/RowNumberLimitDialog.js b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/RowNumberLimitDialog.js
new file mode 100644
index 0000000000..db3ae5a2ea
--- /dev/null
+++ b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/grid/RowNumberLimitDialog.js
@@ -0,0 +1,96 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+define([
+ "dojo/_base/declare",
+ "dojo/_base/event",
+ "dojo/_base/array",
+ "dojo/_base/lang",
+ "dojo/parser",
+ "dojo/dom-construct",
+ "dojo/query",
+ "dijit/registry",
+ "dijit/form/Button",
+ "dijit/form/CheckBox",
+ "dojox/grid/enhanced/plugins/Dialog",
+ "dojo/text!../../../grid/showRowNumberLimitDialog.html",
+ "dojo/domReady!"
+], function(declare, event, array, lang, parser, dom, query, registry, Button, CheckBox, Dialog, template ){
+
+
+return declare("qpid.management.logs.RowNumberLimitDialog", null, {
+
+ grid: null,
+ dialog: null,
+
+ constructor: function(domNode, limitChangedCallback){
+
+ this.containerNode = dom.create("div", {innerHTML: template});
+ parser.parse(this.containerNode);
+
+ this.rowNumberLimit = registry.byNode(query(".rowNumberLimit", this.containerNode)[0])
+ this.submitButton = registry.byNode(query(".submitButton", this.containerNode)[0]);
+ this.closeButton = registry.byNode(query(".cancelButton", this.containerNode)[0]);
+
+ this.dialog = new Dialog({
+ "refNode": domNode,
+ "title": "Grid Rows Number",
+ "content": this.containerNode
+ });
+
+ var self = this;
+ this.submitButton.on("click", function(e){
+ if (self.rowNumberLimit.value > 0)
+ {
+ try
+ {
+ limitChangedCallback(self.rowNumberLimit.value);
+ }
+ catch(e)
+ {
+ console.error(e);
+ }
+ finally
+ {
+ self.dialog.hide();
+ }
+ }
+ });
+
+ this.closeButton.on("click", function(e){self.dialog.hide(); });
+ this.dialog.startup();
+ },
+
+ destroy: function(){
+ this.submitButton.destroy();
+ this.closeButton.destroy();
+ this.dialog.destroy();
+ this.dialog = null;
+ },
+
+ showDialog: function(currentLimit){
+ this.rowNumberLimit.set("value", currentLimit);
+ this.dialog.show();
+ }
+
+ });
+
+});
diff --git a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/util.js b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/util.js
index 2c2096d390..86fda92cb5 100644
--- a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/util.js
+++ b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/util.js
@@ -353,5 +353,21 @@ define(["dojo/_base/xhr",
}
};
+ util.errorHandler = function errorHandler(error)
+ {
+ if(error.status == 401)
+ {
+ alert("Authentication Failed");
+ }
+ else if(error.status == 403)
+ {
+ alert("Access Denied");
+ }
+ else
+ {
+ alert(error);
+ }
+ }
+
return util;
}); \ No newline at end of file
diff --git a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/Broker.js b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/Broker.js
index f721ad6fa5..1bb0ca0afa 100644
--- a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/Broker.js
+++ b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/Broker.js
@@ -352,6 +352,12 @@ define(["dojo/_base/xhr",
that.brokerUpdater.update();
+ var logViewerButton = query(".logViewer", contentPane.containerNode)[0];
+ registry.byNode(logViewerButton).on("click", function(evt){
+ that.controller.show("logViewer", null, null);
+ });
+
+
var addProviderButton = query(".addAuthenticationProvider", contentPane.containerNode)[0];
connect.connect(registry.byNode(addProviderButton), "onClick", function(evt){ addAuthenticationProvider.show(); });
@@ -664,43 +670,6 @@ define(["dojo/_base/xhr",
}, gridProperties, EnhancedGrid);
that.displayACLWarnMessage(aclData);
});
-
- xhr.get({url: "rest/logrecords", sync: properties.useSyncGet, handleAs: "json"})
- .then(function(data)
- {
- that.logData = data;
-
- var gridProperties = {
- height: 400,
- plugins: {
- pagination: {
- pageSizes: ["10", "25", "50", "100"],
- description: true,
- sizeSwitch: true,
- pageStepper: true,
- gotoButton: true,
- maxPageStep: 4,
- position: "bottom"
- }
- }};
-
-
- that.logfileGrid =
- new UpdatableStore(that.logData, query(".broker-logfile")[0],
- [ { name: "Timestamp", field: "timestamp", width: "200px",
- formatter: function(val) {
- var d = new Date(0);
- d.setUTCSeconds(val/1000);
-
- return d.toLocaleString();
- }},
- { name: "Level", field: "level", width: "60px"},
- { name: "Logger", field: "logger", width: "280px"},
- { name: "Thread", field: "thread", width: "120px"},
- { name: "Log Message", field: "message", width: "100%"}
-
- ], null, gridProperties, EnhancedGrid);
- });
}
BrokerUpdater.prototype.updateHeader = function()
@@ -805,15 +774,6 @@ define(["dojo/_base/xhr",
that.displayACLWarnMessage(data);
}
});
-
-
- xhr.get({url: "rest/logrecords", sync: properties.useSyncGet, handleAs: "json"})
- .then(function(data)
- {
- that.logData = data;
- that.logfileGrid.update(that.logData);
- });
-
};
BrokerUpdater.prototype.showReadOnlyAttributes = function()
diff --git a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/controller.js b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/controller.js
index b7eddbbb77..b487ab62f2 100644
--- a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/controller.js
+++ b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/controller.js
@@ -35,10 +35,11 @@ define(["dojo/dom",
"qpid/management/AccessControlProvider",
"qpid/management/Port",
"qpid/management/Plugin",
+ "qpid/management/logs/LogViewer",
"dojo/ready",
"dojo/domReady!"],
function (dom, registry, ContentPane, entities, Broker, VirtualHost, Exchange, Queue, Connection, AuthProvider,
- GroupProvider, Group, KeyStore, TrustStore, AccessControlProvider, Port, Plugin, ready) {
+ GroupProvider, Group, KeyStore, TrustStore, AccessControlProvider, Port, Plugin, LogViewer, ready) {
var controller = {};
var constructors = { broker: Broker, virtualhost: VirtualHost, exchange: Exchange,
@@ -46,7 +47,7 @@ define(["dojo/dom",
authenticationprovider: AuthProvider, groupprovider: GroupProvider,
group: Group, keystore: KeyStore, truststore: TrustStore,
accesscontrolprovider: AccessControlProvider, port: Port,
- plugin: Plugin};
+ plugin: Plugin, logViewer: LogViewer};
var tabDiv = dom.byId("managedViews");
diff --git a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/logs/LogFileDownloadDialog.js b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/logs/LogFileDownloadDialog.js
new file mode 100644
index 0000000000..c25fd7c609
--- /dev/null
+++ b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/logs/LogFileDownloadDialog.js
@@ -0,0 +1,175 @@
+/*
+ *
+ * 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.
+ *
+ */
+define([
+ "dojo/_base/declare",
+ "dojo/_base/event",
+ "dojo/_base/xhr",
+ "dojo/_base/connect",
+ "dojo/dom-construct",
+ "dojo/query",
+ "dojo/parser",
+ "dojo/store/Memory",
+ "dojo/data/ObjectStore",
+ "dojo/date/locale",
+ "dojo/number",
+ "dijit/registry",
+ "dijit/Dialog",
+ "dijit/form/Button",
+ "dojox/grid/EnhancedGrid",
+ "dojo/text!../../../logs/showLogFileDownloadDialog.html",
+ "dojo/domReady!"
+], function(declare, event, xhr, connect, domConstruct, query, parser, Memory, ObjectStore, locale, number,
+ registry, Dialog, Button, EnhancedGrid, template){
+
+
+return declare("qpid.management.logs.LogFileDownloadDialog", null, {
+
+ templateString: template,
+ containerNode: null,
+ widgetsInTemplate: true,
+ logFileDialog: null,
+ logFilesGrid: null,
+ downloadLogsButton: null,
+ closeButton: null,
+
+ constructor: function(args){
+ this.containerNode = domConstruct.create("div", {innerHTML: template});
+ parser.parse(this.containerNode);
+
+ this.logFileTreeDiv = query(".logFilesGrid", this.containerNode)[0];
+ this.downloadLogsButton = registry.byNode(query(".downloadLogsButton", this.containerNode)[0]);
+ this.closeButton = registry.byNode(query(".downloadLogsDialogCloseButton", this.containerNode)[0]);
+
+ var self = this;
+ this.closeButton.on("click", function(e){self._onCloseButtonClick(e);});
+ this.downloadLogsButton.on("click", function(e){self._onDownloadButtonClick(e);});
+ this.downloadLogsButton.set("disabled", true)
+
+ this.logFileDialog = new Dialog({
+ title:"Broker Log Files",
+ style: "width: 600px",
+ content: this.containerNode
+ });
+
+ var layout = [
+ { name: "Appender", field: "appenderName", width: "auto"},
+ { name: "Name", field: "name", width: "auto"},
+ { name: "Size", field: "size", width: "60px",
+ formatter: function(val){
+ return val > 1024 ? (val > 1048576? number.round(val/1048576) + "MB": number.round(val/1024) + "KB") : val + "bytes";
+ }
+ },
+ { name: "Last Modified", field: "lastModified", width: "250px",
+ formatter: function(val) {
+ var d = new Date(val);
+ return locale.format(d, {selector:"date", datePattern: "EEE, MMM d yy, HH:mm:ss z (ZZZZ)"});
+ }
+ }
+ ];
+
+ var gridProperties = {
+ store: new ObjectStore({objectStore: new Memory({data: [], idProperty: "id"}) }),
+ structure: layout,
+ autoHeight: true,
+ plugins: {
+ pagination: {
+ pageSizes: [10, 25, 50, 100],
+ description: true,
+ sizeSwitch: true,
+ pageStepper: true,
+ gotoButton: true,
+ maxPageStep: 4,
+ position: "bottom"
+ },
+ indirectSelection: {
+ headerSelector:true,
+ width:"20px",
+ styles:"text-align: center;"
+ }
+ }
+ };
+
+ this.logFilesGrid = new EnhancedGrid(gridProperties, this.logFileTreeDiv);
+ var self = this;
+ var downloadButtonToggler = function(rowIndex){
+ var data = self.logFilesGrid.selection.getSelected();
+ self.downloadLogsButton.set("disabled",!data.length );
+ };
+ connect.connect(this.logFilesGrid.selection, 'onSelected', downloadButtonToggler);
+ connect.connect(this.logFilesGrid.selection, 'onDeselected', downloadButtonToggler);
+ },
+
+ _onCloseButtonClick: function(evt){
+ event.stop(evt);
+ this.logFileDialog.hide();
+ },
+
+ _onDownloadButtonClick: function(evt){
+ event.stop(evt);
+ var data = this.logFilesGrid.selection.getSelected();
+ if (data.length)
+ {
+ var query = "";
+ for(var i = 0 ; i< data.length; i++)
+ {
+ if (i>0)
+ {
+ query+="&";
+ }
+ query+="l="+encodeURIComponent(data[i].appenderName +'/' + data[i].name);
+ }
+ window.location="rest/logfile?" + query;
+ this.logFileDialog.hide();
+ }
+ },
+
+ destroy: function(){
+ this.inherited(arguments);
+ if (this.logFileDialog)
+ {
+ this.logFileDialog.destroyRecursive();
+ this.logFileDialog = null;
+ }
+ },
+
+ showDialog: function(){
+ var self = this;
+ var requestArguments = {url: "rest/logfiles", sync: true, handleAs: "json"};
+ xhr.get(requestArguments).then(function(data){
+ try
+ {
+ self.logFilesGrid.store.objectStore.setData(data);
+ self.logFilesGrid.startup();
+ self.logFileDialog.startup();
+ self.logFileDialog.show();
+ self.logFilesGrid._refresh();
+
+ }
+ catch(e)
+ {
+ console.error(e);
+ }
+ });
+ }
+
+ });
+
+});
diff --git a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/logs/LogViewer.js b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/logs/LogViewer.js
new file mode 100644
index 0000000000..4bec7440ab
--- /dev/null
+++ b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/logs/LogViewer.js
@@ -0,0 +1,202 @@
+/*
+ *
+ * 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.
+ *
+ */
+define(["dojo/_base/xhr",
+ "dojo/parser",
+ "dojo/query",
+ "dojo/date/locale",
+ "dijit/registry",
+ "qpid/common/grid/GridUpdater",
+ "qpid/management/logs/LogFileDownloadDialog",
+ "dojo/text!../../../logs/showLogViewer.html",
+ "dojo/domReady!"],
+ function (xhr, parser, query, locale, registry, GridUpdater, LogFileDownloadDialog, markup) {
+
+ var defaulGridRowLimit = 4096;
+
+ function LogViewer(name, parent, controller) {
+ var self = this;
+
+ this.name = name;
+ this.lastLogId = 0;
+ this.contentPane = null;
+ this.downloadLogsButton = null;
+ this.downloadLogDialog = null;
+ }
+
+ LogViewer.prototype.getTitle = function() {
+ return "Log Viewer";
+ };
+
+ LogViewer.prototype.open = function(contentPane) {
+ var self = this;
+ this.contentPane = contentPane;
+ this.contentPane.containerNode.innerHTML = markup;
+
+ parser.parse(this.contentPane.containerNode);
+
+ this.downloadLogsButton = registry.byNode(query(".downloadLogs", contentPane.containerNode)[0]);
+ this.downloadLogDialog = new LogFileDownloadDialog();
+
+ this.downloadLogsButton.on("click", function(evt){
+ self.downloadLogDialog.showDialog();
+ });
+
+ var gridStructure = [
+ {
+ hidden: true,
+ name: "ID",
+ field: "id",
+ width: "50px",
+ datatype: "number",
+ filterable: true
+ },
+ {
+ name: "Date", field: "timestamp", width: "100px", datatype: "date",
+ formatter: function(val) {
+ var d = new Date(0);
+ d.setUTCSeconds(val/1000);
+ return locale.format(d, {selector:"date", datePattern: "EEE, MMM d yy"});
+ },
+ dataTypeArgs: {
+ selector: "date",
+ datePattern: "EEE MMMM d yyy"
+ }
+ },
+ { name: "Time", field: "timestamp", width: "150px", datatype: "time",
+ formatter: function(val) {
+ var d = new Date(0);
+ d.setUTCSeconds(val/1000);
+ return locale.format(d, {selector:"time", timePattern: "HH:mm:ss z (ZZZZ)"});
+ },
+ dataTypeArgs: {
+ selector: "time",
+ timePattern: "HH:mm:ss ZZZZ"
+ }
+ },
+ { name: "Level", field: "level", width: "50px", datatype: "string", autoComplete: true, hidden: true},
+ { name: "Logger", field: "logger", width: "150px", datatype: "string", autoComplete: false, hidden: true},
+ { name: "Thread", field: "thread", width: "100px", datatype: "string", hidden: true},
+ { name: "Log Message", field: "message", width: "auto", datatype: "string"}
+ ];
+
+ this._buildGrid(gridStructure);
+ };
+
+ LogViewer.prototype._buildGrid = function(gridStructure) {
+ var self = this;
+ var gridNode = query("#broker-logfile", this.contentPane.containerNode)[0];
+ try
+ {
+ this.updater = new GridUpdater({
+ updatable: false,
+ serviceUrl: function()
+ {
+ return "rest/logrecords?lastLogId=" + self.lastLogId;
+ },
+ onUpdate: function(items)
+ {
+ if (items)
+ {
+ var maxId = -1;
+ for(var i in items)
+ {
+ var item = items[i];
+ if (item.id > maxId)
+ {
+ maxId = item.id
+ }
+ }
+ if (maxId != -1)
+ {
+ self.lastLogId = maxId
+ }
+ }
+ },
+ append: true,
+ appendLimit: defaulGridRowLimit,
+ node: gridNode,
+ structure: gridStructure,
+ gridProperties: {
+ selectable: true,
+ selectionMode: "none",
+ sortInfo: -1,
+ sortFields: [{attribute: 'timestamp', descending: true}],
+ plugins:{
+ nestedSorting:true,
+ enhancedFilter:{defaulGridRowLimit: defaulGridRowLimit},
+ indirectSelection: false
+ }
+ },
+ funct: function (obj)
+ {
+ var onStyleRow = function(row)
+ {
+ var item = obj.grid.getItem(row.index);
+ if(item){
+ var level = obj.dataStore.getValue(item, "level", null);
+ var changed = false;
+ if(level == "ERROR"){
+ row.customClasses += " redBackground";
+ changed = true;
+ } else if(level == "WARN"){
+ row.customClasses += " yellowBackground";
+ changed = true;
+ } else if(level == "DEBUG"){
+ row.customClasses += " grayBackground";
+ changed = true;
+ }
+ if (changed)
+ {
+ obj.grid.focus.styleRow(row);
+ }
+ }
+ };
+ obj.grid.on("styleRow", onStyleRow);
+ obj.grid.startup();
+ }
+ });
+ }
+ catch(err)
+ {
+ console.error(err);
+ }
+ };
+
+ LogViewer.prototype.close = function() {
+ if (this.updater)
+ {
+ this.updater.destroy();
+ this.updater = null;
+ }
+ if (this.downloadLogDialog)
+ {
+ this.downloadLogDialog.destroy();
+ this.downloadLogDialog = null;
+ }
+ if (this.downloadLogsButton)
+ {
+ this.downloadLogsButton.destroy();
+ this.downloadLogsButton = null;
+ }
+ };
+
+ return LogViewer;
+ });
diff --git a/java/broker-plugins/management-http/src/main/java/resources/logs/showLogFileDownloadDialog.html b/java/broker-plugins/management-http/src/main/java/resources/logs/showLogFileDownloadDialog.html
new file mode 100644
index 0000000000..bc633d059a
--- /dev/null
+++ b/java/broker-plugins/management-http/src/main/java/resources/logs/showLogFileDownloadDialog.html
@@ -0,0 +1,33 @@
+<!--
+ -
+ - 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.
+ -
+ -->
+<div>
+ <div class="contentArea" style="height:320px;overflow:auto">
+ <div><b>Select log files to download</b></div>
+ <div class="logFilesGrid" style='height:300px;width: 580px'></div>
+ </div>
+ <div class="dijitDialogPaneActionBar">
+ <button value="Download" data-dojo-type="dijit.form.Button"
+ class="downloadLogsButton"
+ data-dojo-props="iconClass: 'downloadLogsIcon', label: 'Download' "></button>
+ <button value="Close" data-dojo-type="dijit.form.Button" data-dojo-props="label: 'Close'"
+ class="downloadLogsDialogCloseButton"></button>
+ </div>
+</div>
diff --git a/java/broker-plugins/management-http/src/main/java/resources/logs/showLogViewer.html b/java/broker-plugins/management-http/src/main/java/resources/logs/showLogViewer.html
new file mode 100644
index 0000000000..10ac09a406
--- /dev/null
+++ b/java/broker-plugins/management-http/src/main/java/resources/logs/showLogViewer.html
@@ -0,0 +1,29 @@
+<!--
+ -
+ - 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.
+ -
+ -->
+<div class="logViewer">
+
+ <div id="broker-logfile"></div>
+ <br/>
+ <button data-dojo-type="dijit.form.Button" class="downloadLogs"
+ data-dojo-props="iconClass: 'downloadLogsIcon', title:'Download Log Files', name: 'downloadLogs'">Download</button>
+ <br/>
+</div>
+
diff --git a/java/broker-plugins/management-http/src/main/java/resources/showBroker.html b/java/broker-plugins/management-http/src/main/java/resources/showBroker.html
index d9991452af..366ef27c8a 100644
--- a/java/broker-plugins/management-http/src/main/java/resources/showBroker.html
+++ b/java/broker-plugins/management-http/src/main/java/resources/showBroker.html
@@ -190,9 +190,7 @@
<button data-dojo-type="dijit.form.Button" class="deleteAccessControlProvider">Delete Access Control Provider</button>
</div>
<br/>
- <div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Log File', open: false">
- <div class="broker-logfile"></div>
- </div>
- <br/>
+ <button data-dojo-type="dijit.form.Button" class="logViewer" data-dojo-props="iconClass: 'logViewerIcon'">Log Viewer</button>
+ <br/><br/>
</div>
diff --git a/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/log/LogFileHelperTest.java b/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/log/LogFileHelperTest.java
new file mode 100644
index 0000000000..608ef28f02
--- /dev/null
+++ b/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/log/LogFileHelperTest.java
@@ -0,0 +1,339 @@
+/*
+ *
+ * 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.qpid.server.management.plugin.log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.DailyRollingFileAppender;
+import org.apache.log4j.FileAppender;
+import org.apache.log4j.QpidCompositeRollingAppender;
+import org.apache.log4j.RollingFileAppender;
+import org.apache.log4j.varia.ExternallyRolledFileAppender;
+import org.apache.qpid.test.utils.QpidTestCase;
+import org.apache.qpid.test.utils.TestFileUtils;
+import org.apache.qpid.util.FileUtils;
+
+public class LogFileHelperTest extends QpidTestCase
+{
+ private Map<String, List<File>> _appendersFiles;
+ private File _compositeRollingAppenderBackupFolder;
+ private List<Appender> _appenders;
+ private LogFileHelper _helper;
+
+ public void setUp() throws Exception
+ {
+ super.setUp();
+ _appendersFiles = new HashMap<String, List<File>>();
+ _compositeRollingAppenderBackupFolder = new File(TMP_FOLDER, "_compositeRollingAppenderBackupFolder");
+ _compositeRollingAppenderBackupFolder.mkdirs();
+
+ _appendersFiles.put(FileAppender.class.getSimpleName(),
+ Collections.singletonList(TestFileUtils.createTempFile(this, ".log", "FileAppender")));
+ _appendersFiles.put(DailyRollingFileAppender.class.getSimpleName(),
+ Collections.singletonList(TestFileUtils.createTempFile(this, ".log", "DailyRollingFileAppender")));
+ _appendersFiles.put(RollingFileAppender.class.getSimpleName(),
+ Collections.singletonList(TestFileUtils.createTempFile(this, ".log", "RollingFileAppender")));
+ _appendersFiles.put(ExternallyRolledFileAppender.class.getSimpleName(),
+ Collections.singletonList(TestFileUtils.createTempFile(this, ".log", "ExternallyRolledFileAppender")));
+
+ File file = TestFileUtils.createTempFile(this, ".log", "QpidCompositeRollingAppender");
+ File backUpFile = File.createTempFile(file.getName() + ".", ".1." + LogFileHelper.GZIP_EXTENSION);
+ _appendersFiles.put(QpidCompositeRollingAppender.class.getSimpleName(), Arrays.asList(file, backUpFile));
+
+ FileAppender fileAppender = new FileAppender();
+ DailyRollingFileAppender dailyRollingFileAppender = new DailyRollingFileAppender();
+ RollingFileAppender rollingFileAppender = new RollingFileAppender();
+ ExternallyRolledFileAppender externallyRolledFileAppender = new ExternallyRolledFileAppender();
+ QpidCompositeRollingAppender qpidCompositeRollingAppender = new QpidCompositeRollingAppender();
+ qpidCompositeRollingAppender.setbackupFilesToPath(_compositeRollingAppenderBackupFolder.getPath());
+
+ _appenders = new ArrayList<Appender>();
+ _appenders.add(fileAppender);
+ _appenders.add(dailyRollingFileAppender);
+ _appenders.add(rollingFileAppender);
+ _appenders.add(externallyRolledFileAppender);
+ _appenders.add(qpidCompositeRollingAppender);
+
+ for (Appender appender : _appenders)
+ {
+ FileAppender fa = (FileAppender) appender;
+ fa.setName(fa.getClass().getSimpleName());
+ fa.setFile(_appendersFiles.get(fa.getClass().getSimpleName()).get(0).getPath());
+ }
+
+ _helper = new LogFileHelper(_appenders);
+ }
+
+ public void tearDown() throws Exception
+ {
+ try
+ {
+ for (List<File> files : _appendersFiles.values())
+ {
+ for (File file : files)
+ {
+ try
+ {
+ FileUtils.delete(file, false);
+ }
+ catch (Exception e)
+ {
+ // ignore
+ }
+ }
+ }
+ FileUtils.delete(_compositeRollingAppenderBackupFolder, true);
+ }
+ finally
+ {
+ super.tearDown();
+ }
+ }
+
+ public void testGetLogFileDetailsWithLocations() throws Exception
+ {
+ List<LogFileDetails> details = _helper.getLogFileDetails(true);
+
+ assertLogFiles(details, true);
+ }
+
+ public void testGetLogFileDetailsWithoutLocations() throws Exception
+ {
+ List<LogFileDetails> details = _helper.getLogFileDetails(false);
+
+ assertLogFiles(details, false);
+ }
+
+ public void testWriteLogFilesForAllLogs() throws Exception
+ {
+ List<LogFileDetails> details = _helper.getLogFileDetails(true);
+ File f = TestFileUtils.createTempFile(this, ".zip");
+
+ FileOutputStream os = new FileOutputStream(f);
+ try
+ {
+ _helper.writeLogFiles(details, os);
+ }
+ finally
+ {
+ if (os != null)
+ {
+ os.close();
+ }
+ }
+
+ assertWrittenFile(f, details);
+ }
+
+ public void testWriteLogFile() throws Exception
+ {
+ File file = _appendersFiles.get(FileAppender.class.getSimpleName()).get(0);
+
+ File f = TestFileUtils.createTempFile(this, ".log");
+ FileOutputStream os = new FileOutputStream(f);
+ try
+ {
+ _helper.writeLogFile(file, os);
+ }
+ finally
+ {
+ if (os != null)
+ {
+ os.close();
+ }
+ }
+
+ assertEquals("Unexpected log content", FileAppender.class.getSimpleName(), FileUtils.readFileAsString(f));
+ }
+
+ public void testFindLogFileDetails()
+ {
+ String[] logFileDisplayedPaths = new String[6];
+ File[] files = new File[logFileDisplayedPaths.length];
+ int i = 0;
+ for (Map.Entry<String, List<File>> entry : _appendersFiles.entrySet())
+ {
+ String appenderName = entry.getKey();
+ List<File> appenderFiles = entry.getValue();
+ for (File logFile : appenderFiles)
+ {
+ logFileDisplayedPaths[i] = appenderName + "/" + logFile.getName();
+ files[i++] = logFile;
+ }
+ }
+
+ List<LogFileDetails> logFileDetails = _helper.findLogFileDetails(logFileDisplayedPaths);
+ assertEquals("Unexpected details size", logFileDisplayedPaths.length, logFileDetails.size());
+
+ boolean gzipFileFound = false;
+ for (int j = 0; j < logFileDisplayedPaths.length; j++)
+ {
+ String displayedPath = logFileDisplayedPaths[j];
+ String[] parts = displayedPath.split("/");
+ LogFileDetails d = logFileDetails.get(j);
+ assertEquals("Unexpected name", parts[1], d.getName());
+ assertEquals("Unexpected appender", parts[0], d.getAppenderName());
+ if (files[j].getName().endsWith(LogFileHelper.GZIP_EXTENSION))
+ {
+ assertEquals("Unexpected mime type for gz file", LogFileHelper.GZIP_MIME_TYPE, d.getMimeType());
+ gzipFileFound = true;
+ }
+ else
+ {
+ assertEquals("Unexpected mime type", LogFileHelper.TEXT_MIME_TYPE, d.getMimeType());
+ }
+ assertEquals("Unexpecte file location", files[j], d.getLocation());
+ assertEquals("Unexpecte file size", files[j].length(), d.getSize());
+ assertEquals("Unexpecte file last modified date", files[j].lastModified(), d.getLastModified());
+ }
+ assertTrue("Gzip log file is not found", gzipFileFound);
+ }
+
+ public void testFindLogFileDetailsForNotExistingAppender()
+ {
+ String[] logFileDisplayedPaths = { "NotExistingAppender/qpid.log" };
+ List<LogFileDetails> details = _helper.findLogFileDetails(logFileDisplayedPaths);
+ assertTrue("No details should be created for non-existing appender", details.isEmpty());
+ }
+
+ public void testFindLogFileDetailsForNotExistingFile()
+ {
+ String[] logFileDisplayedPaths = { "FileAppender/qpid-non-existing.log" };
+ List<LogFileDetails> details = _helper.findLogFileDetails(logFileDisplayedPaths);
+ assertTrue("No details should be created for non-existing file", details.isEmpty());
+ }
+
+ public void testFindLogFileDetailsForIncorectlySpecifiedLogFilePath()
+ {
+ String[] logFileDisplayedPaths = { "FileAppender\\" + _appendersFiles.get("FileAppender").get(0).getName() };
+ try
+ {
+ _helper.findLogFileDetails(logFileDisplayedPaths);
+ fail("Exception is expected for incorectly set path to log file");
+ }
+ catch (IllegalArgumentException e)
+ {
+ // pass
+ }
+ }
+
+ private void assertLogFiles(List<LogFileDetails> details, boolean includeLocation)
+ {
+ for (Map.Entry<String, List<File>> appenderData : _appendersFiles.entrySet())
+ {
+ String appenderName = (String) appenderData.getKey();
+ List<File> files = appenderData.getValue();
+
+ for (File logFile : files)
+ {
+ String logFileName = logFile.getName();
+ LogFileDetails d = findLogFileDetails(logFileName, appenderName, details);
+ assertNotNull("Log file " + logFileName + " is not found for appender " + appenderName, d);
+ if (includeLocation)
+ {
+ assertEquals("Log file " + logFileName + " is different in appender " + appenderName, d.getLocation(),
+ logFile);
+ }
+ }
+ }
+ }
+
+ private LogFileDetails findLogFileDetails(String logFileName, String appenderName, List<LogFileDetails> logFileDetails)
+ {
+ LogFileDetails d = null;
+ for (LogFileDetails lfd : logFileDetails)
+ {
+ if (lfd.getName().equals(logFileName) && lfd.getAppenderName().equals(appenderName))
+ {
+ d = lfd;
+ break;
+ }
+ }
+ return d;
+ }
+
+ private void assertWrittenFile(File f, List<LogFileDetails> details) throws FileNotFoundException, IOException
+ {
+ FileInputStream fis = new FileInputStream(f);
+ try
+ {
+ ZipInputStream zis = new ZipInputStream(fis);
+ ZipEntry ze = zis.getNextEntry();
+
+ while (ze != null)
+ {
+ String entryName = ze.getName();
+ String[] parts = entryName.split("/");
+
+ String appenderName = parts[0];
+ String logFileName = parts[1];
+
+ LogFileDetails d = findLogFileDetails(logFileName, appenderName, details);
+
+ assertNotNull("Unexpected entry " + entryName, d);
+ details.remove(d);
+
+ File logFile = d.getLocation();
+ String logContent = FileUtils.readFileAsString(logFile);
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ byte[] buffer = new byte[1024];
+ int len;
+ while ((len = zis.read(buffer)) > 0)
+ {
+ baos.write(buffer, 0, len);
+ }
+ baos.close();
+
+ assertEquals("Unexpected log file content", logContent, baos.toString());
+
+ ze = zis.getNextEntry();
+ }
+
+ zis.closeEntry();
+ zis.close();
+
+ }
+ finally
+ {
+ if (fis != null)
+ {
+ fis.close();
+ }
+ }
+ assertEquals("Not all log files have been output", 0, details.size());
+ }
+
+}
diff --git a/java/broker/etc/broker_example.acl b/java/broker/etc/broker_example.acl
index 6cab707a89..29dca90f15 100644
--- a/java/broker/etc/broker_example.acl
+++ b/java/broker/etc/broker_example.acl
@@ -76,6 +76,9 @@ ACL ALLOW-LOG webadmins UPDATE METHOD
# authorise operations changing broker model
ACL ALLOW-LOG webadmins CONFIGURE BROKER
+# authorise operations to view and download broker logs
+ACL ALLOW webadmins ACCESS_LOGS BROKER
+
# at the moment only the following UPDATE METHOD rules are supported by web management console
#ACL ALLOW-LOG webadmins UPDATE METHOD component="VirtualHost.Queue" name="moveMessages"
#ACL ALLOW-LOG webadmins UPDATE METHOD component="VirtualHost.Queue" name="copyMessages"
diff --git a/java/broker/src/main/java/org/apache/qpid/server/configuration/BrokerProperties.java b/java/broker/src/main/java/org/apache/qpid/server/configuration/BrokerProperties.java
index 0b31f5b81a..fb382a8ca9 100644
--- a/java/broker/src/main/java/org/apache/qpid/server/configuration/BrokerProperties.java
+++ b/java/broker/src/main/java/org/apache/qpid/server/configuration/BrokerProperties.java
@@ -54,6 +54,7 @@ public class BrokerProperties
public static final String PROPERTY_QPID_HOME = "QPID_HOME";
public static final String PROPERTY_QPID_WORK = "QPID_WORK";
+ public static final String PROPERTY_LOG_RECORDS_BUFFER_SIZE = "qpid.broker_log_records_buffer_size";
private BrokerProperties()
{
diff --git a/java/broker/src/main/java/org/apache/qpid/server/logging/LogRecorder.java b/java/broker/src/main/java/org/apache/qpid/server/logging/LogRecorder.java
index 5528a05360..dfffbdbb5f 100644
--- a/java/broker/src/main/java/org/apache/qpid/server/logging/LogRecorder.java
+++ b/java/broker/src/main/java/org/apache/qpid/server/logging/LogRecorder.java
@@ -25,15 +25,17 @@ import org.apache.log4j.spi.ErrorHandler;
import org.apache.log4j.spi.Filter;
import org.apache.log4j.spi.LoggingEvent;
import org.apache.log4j.spi.ThrowableInformation;
+import org.apache.qpid.server.configuration.BrokerProperties;
public class LogRecorder implements Appender, Iterable<LogRecorder.Record>
{
+ private static final int DEFAULT_BUFFER_SIZE = 4096;
private ErrorHandler _errorHandler;
private Filter _filter;
private String _name;
private long _recordId;
- private final int _bufferSize = 4096;
+ private final int _bufferSize = Integer.getInteger(BrokerProperties.PROPERTY_LOG_RECORDS_BUFFER_SIZE, DEFAULT_BUFFER_SIZE);
private final int _mask = _bufferSize - 1;
private Record[] _records = new Record[_bufferSize];
diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/SecurityManager.java b/java/broker/src/main/java/org/apache/qpid/server/security/SecurityManager.java
index 09e79d3ae9..931368cb97 100755
--- a/java/broker/src/main/java/org/apache/qpid/server/security/SecurityManager.java
+++ b/java/broker/src/main/java/org/apache/qpid/server/security/SecurityManager.java
@@ -44,6 +44,7 @@ import static org.apache.qpid.server.security.access.ObjectType.METHOD;
import static org.apache.qpid.server.security.access.ObjectType.QUEUE;
import static org.apache.qpid.server.security.access.ObjectType.USER;
import static org.apache.qpid.server.security.access.ObjectType.VIRTUALHOST;
+import static org.apache.qpid.server.security.access.Operation.ACCESS_LOGS;
import static org.apache.qpid.server.security.access.Operation.BIND;
import static org.apache.qpid.server.security.access.Operation.CONFIGURE;
import static org.apache.qpid.server.security.access.Operation.CONSUME;
@@ -629,4 +630,15 @@ public class SecurityManager implements ConfigurationChangeListener
});
}
+ public boolean authoriseLogsAccess()
+ {
+ return checkAllPlugins(new AccessCheck()
+ {
+ Result allowed(AccessControl plugin)
+ {
+ return plugin.authorise(ACCESS_LOGS, BROKER, ObjectProperties.EMPTY);
+ }
+ });
+ }
+
}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/access/ObjectType.java b/java/broker/src/main/java/org/apache/qpid/server/security/access/ObjectType.java
index 048d9a8fc9..9016205d1c 100644
--- a/java/broker/src/main/java/org/apache/qpid/server/security/access/ObjectType.java
+++ b/java/broker/src/main/java/org/apache/qpid/server/security/access/ObjectType.java
@@ -19,6 +19,7 @@
package org.apache.qpid.server.security.access;
import static org.apache.qpid.server.security.access.Operation.ACCESS;
+import static org.apache.qpid.server.security.access.Operation.ACCESS_LOGS;
import static org.apache.qpid.server.security.access.Operation.BIND;
import static org.apache.qpid.server.security.access.Operation.CONFIGURE;
import static org.apache.qpid.server.security.access.Operation.CONSUME;
@@ -50,7 +51,7 @@ public enum ObjectType
METHOD(Operation.ALL, ACCESS, UPDATE),
USER(Operation.ALL, CREATE, DELETE, UPDATE),
GROUP(Operation.ALL, CREATE, DELETE, UPDATE),
- BROKER(Operation.ALL, CONFIGURE);
+ BROKER(Operation.ALL, CONFIGURE, ACCESS_LOGS);
private EnumSet<Operation> _actions;
diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/access/Operation.java b/java/broker/src/main/java/org/apache/qpid/server/security/access/Operation.java
index df5289b7bf..db5b8fba11 100644
--- a/java/broker/src/main/java/org/apache/qpid/server/security/access/Operation.java
+++ b/java/broker/src/main/java/org/apache/qpid/server/security/access/Operation.java
@@ -33,7 +33,8 @@ public enum Operation
DELETE,
PURGE,
UPDATE,
- CONFIGURE;
+ CONFIGURE,
+ ACCESS_LOGS;
public static Operation parse(String text)
{
diff --git a/java/systests/src/main/java/org/apache/qpid/systest/rest/LogRecordsRestTest.java b/java/systests/src/main/java/org/apache/qpid/systest/rest/LogRecordsRestTest.java
index a2f9d3189c..8f2c138869 100644
--- a/java/systests/src/main/java/org/apache/qpid/systest/rest/LogRecordsRestTest.java
+++ b/java/systests/src/main/java/org/apache/qpid/systest/rest/LogRecordsRestTest.java
@@ -39,4 +39,25 @@ public class LogRecordsRestTest extends QpidRestTestCase
assertEquals("Unexpected thread", "main", record.get("thread"));
assertEquals("Unexpected logger", "qpid.message.broker.ready", record.get("logger"));
}
+
+ public void testGetLogsFromGivenId() throws Exception
+ {
+ List<Map<String, Object>> logs = getRestTestHelper().getJsonAsList("/rest/logrecords");
+ assertNotNull("Logs data cannot be null", logs);
+ assertTrue("Logs are not found", logs.size() > 0);
+
+ Map<String, Object> lastLog = logs.get(logs.size() -1);
+ Object lastId = lastLog.get("id");
+
+ //make sure that new logs are created
+ getConnection();
+
+ List<Map<String, Object>> newLogs = getRestTestHelper().getJsonAsList("/rest/logrecords?lastLogId=" + lastId);
+ assertNotNull("Logs data cannot be null", newLogs);
+ assertTrue("Logs are not found", newLogs.size() > 0);
+
+ Object nextId = newLogs.get(0).get("id");
+
+ assertEquals("Unexpected next log id", ((Number)lastId).longValue() + 1, ((Number)nextId).longValue());
+ }
}
diff --git a/java/systests/src/main/java/org/apache/qpid/systest/rest/LogViewerTest.java b/java/systests/src/main/java/org/apache/qpid/systest/rest/LogViewerTest.java
new file mode 100644
index 0000000000..6166e8afc1
--- /dev/null
+++ b/java/systests/src/main/java/org/apache/qpid/systest/rest/LogViewerTest.java
@@ -0,0 +1,105 @@
+/*
+ *
+ * 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.qpid.systest.rest;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import org.apache.qpid.server.BrokerOptions;
+
+public class LogViewerTest extends QpidRestTestCase
+{
+ public static final String DEFAULT_FILE_APPENDER_NAME = "FileAppender";
+ private String _expectedLogFileName;
+
+ public void setUp() throws Exception
+ {
+ setSystemProperty("logsuffix", "-" + getTestQueueName());
+ _expectedLogFileName = System.getProperty("logprefix", "") + "qpid" + System.getProperty("logsuffix", "") + ".log";
+
+ // use real broker log file
+ File brokerLogFile = new File(System.getProperty(QPID_HOME), BrokerOptions.DEFAULT_LOG_CONFIG_FILE);
+ setBrokerCommandLog4JFile(brokerLogFile);
+
+ super.setUp();
+ }
+
+ public void testGetLogFiles() throws Exception
+ {
+ List<Map<String, Object>> logFiles = getRestTestHelper().getJsonAsList("/rest/logfiles");
+ assertNotNull("Log files data cannot be null", logFiles);
+
+ // 1 file appender is configured in QPID default log4j xml:
+ assertEquals("Unexpected number of log files", 1, logFiles.size());
+
+ Map<String, Object> logFileDetails = logFiles.get(0);
+ assertEquals("Unexpected log file name", _expectedLogFileName, logFileDetails.get("name"));
+ assertEquals("Unexpected log file mime type", "text/plain", logFileDetails.get("mimeType"));
+ assertEquals("Unexpected log file appender",DEFAULT_FILE_APPENDER_NAME, logFileDetails.get("appenderName"));
+ assertTrue("Unexpected log file size", ((Number)logFileDetails.get("size")).longValue()>0);
+ assertTrue("Unexpected log file modification time", ((Number)logFileDetails.get("lastModified")).longValue()>0);
+ }
+
+ public void testDownloadExistingLogFiles() throws Exception
+ {
+ byte[] bytes = getRestTestHelper().getBytes("/rest/logfile?l=" + DEFAULT_FILE_APPENDER_NAME + "%2F" + _expectedLogFileName);
+
+ ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(bytes));
+ try
+ {
+ ZipEntry entry = zis.getNextEntry();
+ assertEquals("Unexpected broker log file name", DEFAULT_FILE_APPENDER_NAME + "/" + _expectedLogFileName, entry.getName());
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ byte[] buffer = new byte[1024];
+ int len;
+ while ((len = zis.read(buffer)) > 0)
+ {
+ baos.write(buffer, 0, len);
+ }
+ baos.close();
+ assertTrue("Unexpected broker log file content", new String(baos.toByteArray()).contains("BRK-1004"));
+ assertNull("Unexpepected log file entry", zis.getNextEntry());
+ }
+ finally
+ {
+ zis.close();
+ }
+ }
+
+ public void testDownloadNonExistingLogFiles() throws Exception
+ {
+ int responseCode = getRestTestHelper().submitRequest("/rest/logfile?l=" + DEFAULT_FILE_APPENDER_NAME + "%2F"
+ + _expectedLogFileName + "_" + System.currentTimeMillis(), "GET", null);
+
+ assertEquals("Unexpected response code", 404, responseCode);
+ }
+
+ public void testDownloadNonLogFiles() throws Exception
+ {
+ int responseCode = getRestTestHelper().submitRequest("/rest/logfile?l=config.json", "GET", null);
+ assertEquals("Unexpected response code", 400, responseCode);
+ }
+}
diff --git a/java/systests/src/main/java/org/apache/qpid/systest/rest/RestTestHelper.java b/java/systests/src/main/java/org/apache/qpid/systest/rest/RestTestHelper.java
index c15e5d7285..7d99b30049 100644
--- a/java/systests/src/main/java/org/apache/qpid/systest/rest/RestTestHelper.java
+++ b/java/systests/src/main/java/org/apache/qpid/systest/rest/RestTestHelper.java
@@ -468,4 +468,11 @@ public class RestTestHelper
connection.disconnect();
return responseCode;
}
+
+ public byte[] getBytes(String path) throws IOException
+ {
+ HttpURLConnection connection = openManagementConnection(path, "GET");
+ connection.connect();
+ return readConnectionInputStream(connection);
+ }
}
diff --git a/java/systests/src/main/java/org/apache/qpid/systest/rest/acl/LogViewerACLTest.java b/java/systests/src/main/java/org/apache/qpid/systest/rest/acl/LogViewerACLTest.java
new file mode 100644
index 0000000000..5a2ebe3e8e
--- /dev/null
+++ b/java/systests/src/main/java/org/apache/qpid/systest/rest/acl/LogViewerACLTest.java
@@ -0,0 +1,100 @@
+/*
+ *
+ * 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.qpid.systest.rest.acl;
+
+import java.io.IOException;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.qpid.server.management.plugin.HttpManagement;
+import org.apache.qpid.server.security.acl.AbstractACLTestCase;
+import org.apache.qpid.systest.rest.QpidRestTestCase;
+import org.apache.qpid.test.utils.TestBrokerConfiguration;
+
+public class LogViewerACLTest extends QpidRestTestCase
+{
+ private static final String ALLOWED_USER = "user1";
+ private static final String DENIED_USER = "user2";
+
+ @Override
+ protected void customizeConfiguration() throws ConfigurationException, IOException
+ {
+ super.customizeConfiguration();
+ getRestTestHelper().configureTemporaryPasswordFile(this, ALLOWED_USER, DENIED_USER);
+
+ AbstractACLTestCase.writeACLFileUtil(this, null,
+ "ACL ALLOW-LOG ALL ACCESS MANAGEMENT",
+ "ACL ALLOW-LOG " + ALLOWED_USER + " ACCESS_LOGS BROKER",
+ "ACL DENY-LOG " + DENIED_USER + " ACCESS_LOGS BROKER",
+ "ACL DENY-LOG ALL ALL");
+
+ getBrokerConfiguration().setObjectAttribute(TestBrokerConfiguration.ENTRY_NAME_HTTP_MANAGEMENT,
+ HttpManagement.HTTP_BASIC_AUTHENTICATION_ENABLED, true);
+ }
+
+ public void testGetLogRecordsAllowed() throws Exception
+ {
+ getRestTestHelper().setUsernameAndPassword(ALLOWED_USER, ALLOWED_USER);
+
+ int responseCode = getRestTestHelper().submitRequest("/rest/logrecords", "GET", null);
+ assertEquals("Access to log records should be allowed", 200, responseCode);
+ }
+
+ public void testGetLogRecordsDenied() throws Exception
+ {
+ getRestTestHelper().setUsernameAndPassword(DENIED_USER, DENIED_USER);
+
+ int responseCode = getRestTestHelper().submitRequest("/rest/logrecords", "GET", null);
+ assertEquals("Access to log records should be denied", 403, responseCode);
+ }
+
+ public void testGetLogFilesAllowed() throws Exception
+ {
+ getRestTestHelper().setUsernameAndPassword(ALLOWED_USER, ALLOWED_USER);
+
+ int responseCode = getRestTestHelper().submitRequest("/rest/logfiles", "GET", null);
+ assertEquals("Access to log files should be allowed", 200, responseCode);
+ }
+
+ public void testGetLogFilesDenied() throws Exception
+ {
+ getRestTestHelper().setUsernameAndPassword(DENIED_USER, DENIED_USER);
+
+ int responseCode = getRestTestHelper().submitRequest("/rest/logfiles", "GET", null);
+ assertEquals("Access to log files should be denied", 403, responseCode);
+ }
+
+ public void testDownloadLogFileAllowed() throws Exception
+ {
+ getRestTestHelper().setUsernameAndPassword(ALLOWED_USER, ALLOWED_USER);
+
+ int responseCode = getRestTestHelper().submitRequest("/rest/logfile?l=appender%2fqpid.log", "GET", null);
+ assertEquals("Access to log files should be allowed", 404, responseCode);
+ }
+
+ public void testDownloadLogFileDenied() throws Exception
+ {
+ getRestTestHelper().setUsernameAndPassword(DENIED_USER, DENIED_USER);
+
+ int responseCode = getRestTestHelper().submitRequest("/rest/logfile?l=appender%2fqpid.log", "GET", null);
+ assertEquals("Access to log files should be denied", 403, responseCode);
+ }
+
+}