From e7d183fb130b3e99ca4447cc363e09c97f8fd376 Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Fri, 17 Sep 2010 13:42:26 +0000 Subject: [PATCH] Add server capabilities to SLC RCP git-svn-id: https://svn.argeo.org/slc/trunk@3773 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc --- .../org.argeo.slc.demo.log4j/log4j.properties | 1 + .../META-INF/MANIFEST.MF | 7 +- .../META-INF/spring/rcp-osgi.xml | 26 ++++ .../META-INF/spring/rcp.xml | 10 ++ .../slc-client-rcp.product | 71 +++++++++- .../org/argeo/slc/client/rcp/Perspective.java | 5 +- .../META-INF/MANIFEST.MF | 9 +- .../META-INF/spring/osgi.xml | 4 + .../META-INF/spring/views.xml | 13 ++ .../org.argeo.slc.client.ui/plugin.xml | 6 + .../slc/client/ui/actions/RefreshAction.java | 21 ++- .../slc/client/ui/views/ResultListView.java | 130 ++++++++++++++++++ .../META-INF/spring/services-osgi.xml | 9 +- .../attachment/FileAttachmentsStorage.java | 11 +- .../slc/core/runtime/SimpleAgentFactory.java | 26 ++++ .../impl/TestManagerServiceAdapter.java | 94 +++++++++++++ .../org/argeo/slc/jsch/AbstractJschTask.java | 2 +- 17 files changed, 429 insertions(+), 16 deletions(-) create mode 100644 eclipse/plugins/org.argeo.slc.client.rcp/META-INF/spring/rcp-osgi.xml create mode 100644 eclipse/plugins/org.argeo.slc.client.rcp/META-INF/spring/rcp.xml create mode 100644 eclipse/plugins/org.argeo.slc.client.ui/src/org/argeo/slc/client/ui/views/ResultListView.java create mode 100644 runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/runtime/SimpleAgentFactory.java create mode 100644 runtime/org.argeo.slc.server/src/main/java/org/argeo/slc/services/impl/TestManagerServiceAdapter.java diff --git a/demo/site/org.argeo.slc.demo.log4j/log4j.properties b/demo/site/org.argeo.slc.demo.log4j/log4j.properties index 339269ee5..14e31935e 100644 --- a/demo/site/org.argeo.slc.demo.log4j/log4j.properties +++ b/demo/site/org.argeo.slc.demo.log4j/log4j.properties @@ -10,6 +10,7 @@ log4j.logger.org.argeo.security.mvc.ArgeoRememberMeServices=WARN log4j.logger.org.argeo.server.mvc=TRACE log4j.logger.org.argeo.slc.client=TRACE +log4j.logger.org.argeo.slc.services.impl=TRACE log4j.logger.org.argeo.slc.jms.JmsAgentProxy=TRACE #log4j.logger.org.argeo.slc.jms.JmsAgent=TRACE diff --git a/eclipse/plugins/org.argeo.slc.client.rcp/META-INF/MANIFEST.MF b/eclipse/plugins/org.argeo.slc.client.rcp/META-INF/MANIFEST.MF index b8e715c91..143e53ea7 100644 --- a/eclipse/plugins/org.argeo.slc.client.rcp/META-INF/MANIFEST.MF +++ b/eclipse/plugins/org.argeo.slc.client.rcp/META-INF/MANIFEST.MF @@ -1,6 +1,6 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 -Bundle-Name: Rcp +Bundle-Name: SLC RCP Bundle-SymbolicName: org.argeo.slc.client.rcp; singleton:=true Bundle-Version: 1.0.0.qualifier Bundle-Activator: org.argeo.slc.client.rcp.ClientRcpPlugin @@ -8,4 +8,7 @@ Require-Bundle: org.eclipse.ui, org.eclipse.core.runtime Bundle-ActivationPolicy: lazy Bundle-RequiredExecutionEnvironment: J2SE-1.5 -Import-Package: org.argeo.slc.client.ui.views +Import-Package: org.argeo.slc.client.ui.views, + org.argeo.slc.core.test.tree;version="0.13.0.SNAPSHOT-r3768", + org.argeo.slc.services;version="0.13.0.SNAPSHOT-r3753", + org.argeo.slc.services.impl;version="0.13.0.SNAPSHOT-r3753" diff --git a/eclipse/plugins/org.argeo.slc.client.rcp/META-INF/spring/rcp-osgi.xml b/eclipse/plugins/org.argeo.slc.client.rcp/META-INF/spring/rcp-osgi.xml new file mode 100644 index 000000000..1cc8114a7 --- /dev/null +++ b/eclipse/plugins/org.argeo.slc.client.rcp/META-INF/spring/rcp-osgi.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/eclipse/plugins/org.argeo.slc.client.rcp/META-INF/spring/rcp.xml b/eclipse/plugins/org.argeo.slc.client.rcp/META-INF/spring/rcp.xml new file mode 100644 index 000000000..a860fd2c6 --- /dev/null +++ b/eclipse/plugins/org.argeo.slc.client.rcp/META-INF/spring/rcp.xml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/eclipse/plugins/org.argeo.slc.client.rcp/slc-client-rcp.product b/eclipse/plugins/org.argeo.slc.client.rcp/slc-client-rcp.product index b091f99a0..27acbf345 100644 --- a/eclipse/plugins/org.argeo.slc.client.rcp/slc-client-rcp.product +++ b/eclipse/plugins/org.argeo.slc.client.rcp/slc-client-rcp.product @@ -13,7 +13,6 @@ - @@ -30,7 +29,10 @@ + + + @@ -39,6 +41,7 @@ + @@ -51,33 +54,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -86,15 +117,32 @@ + + + + + + + + + + + + + + + + + @@ -106,9 +154,18 @@ + + + + + + + + + @@ -128,6 +185,7 @@ + @@ -135,21 +193,32 @@ + + + + + + + + + + + diff --git a/eclipse/plugins/org.argeo.slc.client.rcp/src/org/argeo/slc/client/rcp/Perspective.java b/eclipse/plugins/org.argeo.slc.client.rcp/src/org/argeo/slc/client/rcp/Perspective.java index 8ada2a007..e3b0c4b1a 100644 --- a/eclipse/plugins/org.argeo.slc.client.rcp/src/org/argeo/slc/client/rcp/Perspective.java +++ b/eclipse/plugins/org.argeo.slc.client.rcp/src/org/argeo/slc/client/rcp/Perspective.java @@ -1,6 +1,7 @@ package org.argeo.slc.client.rcp; import org.argeo.slc.client.ui.views.ExecutionModulesView; +import org.argeo.slc.client.ui.views.ResultListView; import org.eclipse.ui.IPageLayout; import org.eclipse.ui.IPerspectiveFactory; @@ -12,7 +13,9 @@ public class Perspective implements IPerspectiveFactory { layout.setFixed(true); layout.addStandaloneView(ExecutionModulesView.ID, false, - IPageLayout.LEFT, 1.0f, editorArea); + IPageLayout.LEFT, 0.5f, editorArea); + layout.addStandaloneView(ResultListView.ID, false, IPageLayout.RIGHT, + 0.5f, editorArea); } } diff --git a/eclipse/plugins/org.argeo.slc.client.ui/META-INF/MANIFEST.MF b/eclipse/plugins/org.argeo.slc.client.ui/META-INF/MANIFEST.MF index fef65b567..aaba9d93e 100644 --- a/eclipse/plugins/org.argeo.slc.client.ui/META-INF/MANIFEST.MF +++ b/eclipse/plugins/org.argeo.slc.client.ui/META-INF/MANIFEST.MF @@ -6,7 +6,8 @@ Bundle-Version: 1.0.0.qualifier Bundle-Activator: org.argeo.slc.client.ui.ClientUiPlugin Require-Bundle: org.eclipse.ui;resolution:=optional, org.eclipse.rap.ui;resolution:=optional, - org.eclipse.core.runtime + org.eclipse.core.runtime;resolution:=optional, + com.springsource.org.hibernate;resolution:=optional Bundle-ActivationPolicy: lazy Bundle-RequiredExecutionEnvironment: J2SE-1.5 Export-Package: org.argeo.slc.client.ui, @@ -17,10 +18,14 @@ Import-Package: org.apache.commons.logging;version="1.1.1", org.argeo.eclipse.ui, org.argeo.slc.build;version="0.13.0.SNAPSHOT-r3685", org.argeo.slc.core.runtime;version="0.13.0.SNAPSHOT-r3701", + org.argeo.slc.core.test.tree;version="0.13.0.SNAPSHOT-r3768", + org.argeo.slc.dao.test.tree;version="0.13.0.SNAPSHOT-r3768", org.argeo.slc.deploy;version="0.13.0.SNAPSHOT-r3724", org.argeo.slc.execution;version="0.13.0.SNAPSHOT-r3685", org.argeo.slc.process;version="0.13.0.SNAPSHOT-r3685", org.argeo.slc.runtime;version="0.13.0.SNAPSHOT-r3685", org.springframework.beans.factory, org.springframework.context, - org.springframework.core.io.support + org.springframework.core.io.support, + org.hibernate.jdbc, + org.hibernate.hql.ast diff --git a/eclipse/plugins/org.argeo.slc.client.ui/META-INF/spring/osgi.xml b/eclipse/plugins/org.argeo.slc.client.ui/META-INF/spring/osgi.xml index 8758543f0..be57f3900 100644 --- a/eclipse/plugins/org.argeo.slc.client.ui/META-INF/spring/osgi.xml +++ b/eclipse/plugins/org.argeo.slc.client.ui/META-INF/spring/osgi.xml @@ -8,4 +8,8 @@ + + + \ No newline at end of file diff --git a/eclipse/plugins/org.argeo.slc.client.ui/META-INF/spring/views.xml b/eclipse/plugins/org.argeo.slc.client.ui/META-INF/spring/views.xml index 1aecce0e3..f21f2d8c0 100644 --- a/eclipse/plugins/org.argeo.slc.client.ui/META-INF/spring/views.xml +++ b/eclipse/plugins/org.argeo.slc.client.ui/META-INF/spring/views.xml @@ -9,6 +9,11 @@ + + + + @@ -16,4 +21,12 @@ + + + + + + + + diff --git a/eclipse/plugins/org.argeo.slc.client.ui/plugin.xml b/eclipse/plugins/org.argeo.slc.client.ui/plugin.xml index f168b05b7..dd8c430dc 100644 --- a/eclipse/plugins/org.argeo.slc.client.ui/plugin.xml +++ b/eclipse/plugins/org.argeo.slc.client.ui/plugin.xml @@ -9,6 +9,12 @@ class="org.argeo.eclipse.spring.SpringExtensionFactory" name="Execution Modules" restorable="true"> + + diff --git a/eclipse/plugins/org.argeo.slc.client.ui/src/org/argeo/slc/client/ui/actions/RefreshAction.java b/eclipse/plugins/org.argeo.slc.client.ui/src/org/argeo/slc/client/ui/actions/RefreshAction.java index 4e5aea7e2..04a4c14a6 100644 --- a/eclipse/plugins/org.argeo.slc.client.ui/src/org/argeo/slc/client/ui/actions/RefreshAction.java +++ b/eclipse/plugins/org.argeo.slc.client.ui/src/org/argeo/slc/client/ui/actions/RefreshAction.java @@ -1,8 +1,9 @@ package org.argeo.slc.client.ui.actions; -import org.argeo.slc.client.ui.views.ExecutionModulesView; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.slc.client.ui.views.ResultListView; import org.eclipse.jface.action.IAction; -import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.viewers.ISelection; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.IWorkbenchWindowActionDelegate; @@ -16,6 +17,7 @@ import org.eclipse.ui.IWorkbenchWindowActionDelegate; * @see IWorkbenchWindowActionDelegate */ public class RefreshAction implements IWorkbenchWindowActionDelegate { + private final static Log log = LogFactory.getLog(RefreshAction.class); private IWorkbenchWindow window; /** @@ -31,11 +33,16 @@ public class RefreshAction implements IWorkbenchWindowActionDelegate { * @see IWorkbenchWindowActionDelegate#run */ public void run(IAction action) { - ExecutionModulesView view = (ExecutionModulesView) window - .getWorkbench().getActiveWorkbenchWindow().getActivePage() - .findView(ExecutionModulesView.ID); - view.getViewer().refresh(); - MessageDialog.openInformation(window.getShell(), "Ui", "Refreshed"); + // ExecutionModulesView view = (ExecutionModulesView) window + // .getWorkbench().getActiveWorkbenchWindow().getActivePage() + // .findView(ExecutionModulesView.ID); + // view.getViewer().refresh(); + // MessageDialog.openInformation(window.getShell(), "Ui", "Refreshed"); + log.info("command"); + ResultListView view = (ResultListView) window.getWorkbench() + .getActiveWorkbenchWindow().getActivePage().findView( + ResultListView.ID); + view.retrieveResults(); } /** diff --git a/eclipse/plugins/org.argeo.slc.client.ui/src/org/argeo/slc/client/ui/views/ResultListView.java b/eclipse/plugins/org.argeo.slc.client.ui/src/org/argeo/slc/client/ui/views/ResultListView.java new file mode 100644 index 000000000..4566ee0c2 --- /dev/null +++ b/eclipse/plugins/org.argeo.slc.client.ui/src/org/argeo/slc/client/ui/views/ResultListView.java @@ -0,0 +1,130 @@ +package org.argeo.slc.client.ui.views; + +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.slc.core.test.tree.ResultAttributes; +import org.argeo.slc.dao.test.tree.TreeTestResultCollectionDao; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.ITableLabelProvider; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; +import org.eclipse.ui.part.ViewPart; + +public class ResultListView extends ViewPart { + private final static Log log = LogFactory.getLog(ResultListView.class); + + public static final String ID = "org.argeo.slc.client.ui.resultListView"; + + private TableViewer viewer; + + private TreeTestResultCollectionDao testResultCollectionDao; + + public void createPartControl(Composite parent) { + Table table = createTable(parent); + viewer = new TableViewer(table); + viewer.setLabelProvider(new ViewLabelProvider()); + viewer.setContentProvider(new ViewContentProvider()); + + viewer.setInput(getViewSite()); + } + + protected Table createTable(Composite parent) { + int style = SWT.SINGLE | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL + | SWT.FULL_SELECTION | SWT.HIDE_SELECTION; + + Table table = new Table(parent, style); + + GridData gridData = new GridData(GridData.FILL_BOTH); + gridData.grabExcessVerticalSpace = true; + gridData.grabExcessHorizontalSpace = true; + gridData.horizontalSpan = 3; + table.setLayoutData(gridData); + + table.setLinesVisible(true); + table.setHeaderVisible(true); + + TableColumn column = new TableColumn(table, SWT.LEFT, 0); + column.setText("Date"); + column.setWidth(200); + + column = new TableColumn(table, SWT.LEFT, 1); + column.setText("UUID"); + column.setWidth(300); + + return table; + } + + protected class ViewContentProvider implements IStructuredContentProvider { + private List lst; + + @SuppressWarnings("unchecked") + public void inputChanged(Viewer arg0, Object arg1, Object arg2) { + if (arg2 instanceof List) { + lst = (List) arg2; + log.trace("result count: " + lst.size()); + } + } + + public void dispose() { + } + + public Object[] getElements(Object obj) { + if (lst == null) + return new Object[0]; + else + return lst.toArray(); + // return + // testResultCollectionDao.listResultAttributes(null).toArray(); + } + } + + protected class ViewLabelProvider extends LabelProvider implements + ITableLabelProvider { + public String getColumnText(Object obj, int index) { + ResultAttributes ra = (ResultAttributes)obj; + switch(index){ + case 0: + return getText(ra.getCloseDate()); + case 1: + return ra.getUuid(); + } + return getText(obj); + } + + public Image getColumnImage(Object obj, int index) { + return null; + } + + } + + public void setFocus() { + viewer.getControl().setFocus(); + } + + public void retrieveResults() { + try { + List lst = testResultCollectionDao + .listResultAttributes(null); + log.info("result count: " + lst.size()); + viewer.setInput(lst); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + public void setTestResultCollectionDao( + TreeTestResultCollectionDao testResultCollectionDao) { + this.testResultCollectionDao = testResultCollectionDao; + } + +} diff --git a/modules/server/org.argeo.slc.server.services/META-INF/spring/services-osgi.xml b/modules/server/org.argeo.slc.server.services/META-INF/spring/services-osgi.xml index 87bc30dd1..0a353a9ce 100644 --- a/modules/server/org.argeo.slc.server.services/META-INF/spring/services-osgi.xml +++ b/modules/server/org.argeo.slc.server.services/META-INF/spring/services-osgi.xml @@ -9,7 +9,14 @@ - + + + org.argeo.slc.core.attachment.AttachmentsStorage + + org.argeo.slc.core.attachment.AttachmentUploader + + + diff --git a/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/attachment/FileAttachmentsStorage.java b/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/attachment/FileAttachmentsStorage.java index 4b4e13259..29d841117 100644 --- a/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/attachment/FileAttachmentsStorage.java +++ b/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/attachment/FileAttachmentsStorage.java @@ -32,9 +32,10 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.argeo.slc.SlcException; import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.io.Resource; public class FileAttachmentsStorage implements AttachmentsStorage, - InitializingBean { + AttachmentUploader, InitializingBean { private final static Log log = LogFactory .getLog(FileAttachmentsStorage.class); @@ -113,6 +114,14 @@ public class FileAttachmentsStorage implements AttachmentsStorage, } + public void upload(Attachment attachment, Resource resource) { + try { + storeAttachment(attachment, resource.getInputStream()); + } catch (IOException e) { + throw new SlcException("Cannot upload attachment " + attachment, e); + } + } + /** For monitoring purposes only */ protected void updateAttachmentToc(Attachment attachment, File file) { Date date = new Date(file.lastModified()); diff --git a/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/runtime/SimpleAgentFactory.java b/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/runtime/SimpleAgentFactory.java new file mode 100644 index 000000000..6688ec35c --- /dev/null +++ b/runtime/org.argeo.slc.core/src/main/java/org/argeo/slc/core/runtime/SimpleAgentFactory.java @@ -0,0 +1,26 @@ +package org.argeo.slc.core.runtime; + +import java.util.List; + +import org.argeo.slc.runtime.SlcAgent; +import org.argeo.slc.runtime.SlcAgentFactory; + +public class SimpleAgentFactory implements SlcAgentFactory { + private List agents; + + public SlcAgent getAgent(String uuid) { + for (SlcAgent agent : agents) + if (agent.getAgentUuid().equals(uuid)) + return agent; + return null; + } + + public void pingAll(List activeAgentIds) { + // do nothing + } + + public void setAgents(List agents) { + this.agents = agents; + } + +} diff --git a/runtime/org.argeo.slc.server/src/main/java/org/argeo/slc/services/impl/TestManagerServiceAdapter.java b/runtime/org.argeo.slc.server/src/main/java/org/argeo/slc/services/impl/TestManagerServiceAdapter.java new file mode 100644 index 000000000..bc916d61d --- /dev/null +++ b/runtime/org.argeo.slc.server/src/main/java/org/argeo/slc/services/impl/TestManagerServiceAdapter.java @@ -0,0 +1,94 @@ +package org.argeo.slc.services.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.slc.core.attachment.Attachment; +import org.argeo.slc.core.attachment.SimpleAttachment; +import org.argeo.slc.core.test.tree.TreeTestResult; +import org.argeo.slc.core.test.tree.TreeTestResultListener; +import org.argeo.slc.msg.test.tree.AddTreeTestResultAttachmentRequest; +import org.argeo.slc.msg.test.tree.CloseTreeTestResultRequest; +import org.argeo.slc.msg.test.tree.CreateTreeTestResultRequest; +import org.argeo.slc.msg.test.tree.ResultPartRequest; +import org.argeo.slc.services.TestManagerService; +import org.argeo.slc.test.TestResultPart; + +public class TestManagerServiceAdapter implements TreeTestResultListener { + + private final Log log = LogFactory.getLog(getClass()); + + private Boolean onlyOnClose = false; + + private TestManagerService testManagerService; + + public void resultPartAdded(TreeTestResult testResult, + TestResultPart testResultPart) { + if (onlyOnClose) + return; + + if (testResult.getResultParts().size() == 1 + && testResult.getResultParts().values().iterator().next() + .getParts().size() == 1) { + CreateTreeTestResultRequest req = new CreateTreeTestResultRequest( + testResult); + + if (log.isTraceEnabled()) + log.trace("Send create result request for result " + + testResult.getUuid()); + + testManagerService.createTreeTestResult(req); + } else { + ResultPartRequest req = new ResultPartRequest(testResult); + + if (log.isTraceEnabled()) + log.trace("Send result parts for result " + + testResult.getUuid()); + + testManagerService.addResultPart(req); + } + } + + public void close(TreeTestResult testResult) { + if (onlyOnClose) { + CreateTreeTestResultRequest req = new CreateTreeTestResultRequest( + testResult); + + if (log.isTraceEnabled()) + log.trace("Send onClose create result request for result " + + testResult.getUuid()); + + testManagerService.createTreeTestResult(req); + } else { + CloseTreeTestResultRequest req = new CloseTreeTestResultRequest( + testResult); + + if (log.isTraceEnabled()) + log.trace("Send close result request for result " + + testResult.getUuid()); + + testManagerService.closeTreeTestResult(req); + + } + } + + public void addAttachment(TreeTestResult testResult, Attachment attachment) { + if (onlyOnClose) + return; + + AddTreeTestResultAttachmentRequest req = new AddTreeTestResultAttachmentRequest(); + req.setResultUuid(testResult.getUuid()); + req.setAttachment((SimpleAttachment) attachment); + testManagerService.addAttachment(req); + + } + + /** Publishes the test result only when it gets closed. */ + public void setOnlyOnClose(Boolean onlyOnClose) { + this.onlyOnClose = onlyOnClose; + } + + public void setTestManagerService(TestManagerService testManagerService) { + this.testManagerService = testManagerService; + } + +} diff --git a/runtime/org.argeo.slc.support.simple/src/main/java/org/argeo/slc/jsch/AbstractJschTask.java b/runtime/org.argeo.slc.support.simple/src/main/java/org/argeo/slc/jsch/AbstractJschTask.java index b27b4b354..030023295 100644 --- a/runtime/org.argeo.slc.support.simple/src/main/java/org/argeo/slc/jsch/AbstractJschTask.java +++ b/runtime/org.argeo.slc.support.simple/src/main/java/org/argeo/slc/jsch/AbstractJschTask.java @@ -71,7 +71,7 @@ public abstract class AbstractJschTask implements Runnable { } } - public final void run() { + public void run() { Session session = openSession(); try { run(session); -- 2.39.2