/*
* NewConnectionSnippetHost.java
*
* Copyright (C) 2009-17 by RStudio, Inc.
*
* Unless you have received this program directly from RStudio pursuant
* to the terms of a commercial license agreement with RStudio, then
* this program is licensed to you under the terms of version 3 of the
* GNU Affero General Public License. This program is distributed WITHOUT
* ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
* AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
*
*/
package org.rstudio.studio.client.workbench.views.connections.ui;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import org.rstudio.core.client.StringUtil;
import org.rstudio.core.client.widget.MessageDialog;
import org.rstudio.core.client.widget.Operation;
import org.rstudio.core.client.widget.OperationWithInput;
import org.rstudio.core.client.widget.ProgressIndicator;
import org.rstudio.core.client.widget.ThemedButton;
import org.rstudio.studio.client.RStudioGinjector;
import org.rstudio.studio.client.common.DelayedProgressRequestCallback;
import org.rstudio.studio.client.server.ServerError;
import org.rstudio.studio.client.workbench.views.connections.model.ConnectionOptions;
import org.rstudio.studio.client.workbench.views.connections.model.ConnectionsServerOperations;
import org.rstudio.studio.client.workbench.views.connections.model.NewConnectionContext.NewConnectionInfo;
import org.rstudio.studio.client.workbench.views.connections.res.NewConnectionSnippetHostResources;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.regexp.shared.MatchResult;
import com.google.gwt.regexp.shared.RegExp;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.TextBoxBase;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.inject.Inject;
public class NewConnectionSnippetHost extends Composite
{
@Inject
private void initialize(ConnectionsServerOperations server)
{
server_ = server;
}
public void onBeforeActivate(Operation operation, NewConnectionInfo info)
{
initialize(operation, info);
}
public void onActivate(ProgressIndicator indicator)
{
}
public void onDeactivate(Operation operation)
{
operation.execute();
}
public NewConnectionSnippetHost()
{
RStudioGinjector.INSTANCE.injectMembers(this);
newConnectionSnippetHostResources_ = GWT.create(NewConnectionSnippetHostResources.class);
initWidget(createWidget());
}
private void initialize(final Operation operation, final NewConnectionInfo info)
{
info_ = info;
parametersPanel_.clear();
parametersPanel_.add(createParameterizedUI(info));
snippetParts_ = parseSnippet(info.getSnippet());
updateCodePanel();
operation.execute();
}
private ArrayList<NewConnectionSnippetParts> parseSnippet(String input) {
ArrayList<NewConnectionSnippetParts> parts = new ArrayList<NewConnectionSnippetParts>();
RegExp regExp = RegExp.compile(pattern_, "g");
for (MatchResult matcher = regExp.exec(input); matcher != null; matcher = regExp.exec(input)) {
if (matcher.getGroupCount() >= 2) {
int order = 0;
try {
order = Integer.parseInt(matcher.getGroup(1));
} catch (NumberFormatException e) {
}
String key = matcher.getGroup(2);
String value = matcher.getGroupCount() >= 4 ? matcher.getGroup(4) : null;
String connStringField = matcher.getGroupCount() >= 6 ? matcher.getGroup(6) : null;
if (value != null) {
value = value.replaceAll("\\$colon\\$", ":");
value = value.replaceAll("\\$equal\\$", "=");
}
parts.add(new NewConnectionSnippetParts(order, key, value, connStringField));
}
}
Collections.sort(parts, new Comparator<NewConnectionSnippetParts>()
{
@Override
public int compare(NewConnectionSnippetParts p1, NewConnectionSnippetParts p2)
{
return p1.getOrder() - p2.getOrder();
}
});
return parts;
}
private static int maxRows_ = 4;
private void showSuccess()
{
VerticalPanel verticalPanel = new VerticalPanel();
verticalPanel.addStyleName(RES.styles().dialogMessagePanel());
HTML msg = new HTML("<b>Success!</b> The given parameters " +
"can be used to connect and disconnect correctly.");
verticalPanel.add(msg);
MessageDialog dlg = new MessageDialog(MessageDialog.INFO,
"Test Results",
verticalPanel
);
dlg.addButton("OK", new Operation() {
@Override
public void execute()
{
}
}, true, false);
dlg.showModal();
}
private void showFailure(String error)
{
VerticalPanel verticalPanel = new VerticalPanel();
verticalPanel.addStyleName(RES.styles().dialogMessagePanel());
SafeHtmlBuilder safeHtmlBuilder = new SafeHtmlBuilder();
safeHtmlBuilder.appendHtmlConstant("<b>Failure.</b> ");
safeHtmlBuilder.appendEscaped(error);
verticalPanel.add(new HTML(safeHtmlBuilder.toSafeHtml()));
MessageDialog dlg = new MessageDialog(MessageDialog.ERROR,
"Test Results",
verticalPanel
);
dlg.addButton("OK", new Operation() {
@Override
public void execute()
{
}
}, true, false);
dlg.showModal();
}
private Grid createParameterizedUI(final NewConnectionInfo info)
{
final ArrayList<NewConnectionSnippetParts> snippetParts = parseSnippet(info.getSnippet());
int visibleRows = snippetParts.size();
int visibleParams = Math.min(visibleRows, maxRows_);
// If we have a field that shares the first row, usually port:
boolean hasSecondaryHeaderField = false;
if (visibleParams >= 2 && snippetParts.get(0).getOrder() == snippetParts.get(1).getOrder()) {
visibleRows --;
visibleParams ++;
hasSecondaryHeaderField = true;
}
boolean showAdvancedButton = visibleRows > maxRows_;
visibleRows = Math.min(visibleRows, maxRows_);
final ArrayList<NewConnectionSnippetParts> secondarySnippetParts =
new ArrayList<NewConnectionSnippetParts>(snippetParts.subList(visibleParams, snippetParts.size()));
final Grid connGrid = new Grid(visibleRows + 1, 4);
connGrid.addStyleName(RES.styles().grid());
connGrid.getCellFormatter().setWidth(0, 0, "150px");
connGrid.getCellFormatter().setWidth(0, 1, "180px");
connGrid.getCellFormatter().setWidth(0, 2, "60px");
connGrid.getCellFormatter().setWidth(0, 3, "74px");
for (int idxParams = 0, idxRow = 0; idxRow < visibleRows; idxParams++, idxRow++) {
connGrid.getRowFormatter().setStyleName(idxRow, RES.styles().gridRow());
final String key = snippetParts.get(idxParams).getKey();
Label label = new Label(key + ":");
label.addStyleName(RES.styles().label());
connGrid.setWidget(idxRow, 0, label);
connGrid.getRowFormatter().setVerticalAlign(idxRow, HasVerticalAlignment.ALIGN_TOP);
String textboxStyle = RES.styles().textbox();
if (idxRow == 0 && hasSecondaryHeaderField) {
textboxStyle = RES.styles().firstTextbox();
} else {
connGrid.getCellFormatter().getElement(idxRow, 1).setAttribute("colspan", "4");
}
final TextBoxBase textboxBase;
if (visibleRows == 1) {
TextArea textarea = new TextArea();
textarea.setVisibleLines(7);
textarea.addStyleName(RES.styles().textarea());
textarea.setText(snippetParts.get(idxParams).getValue());
connGrid.setWidget(idxRow, 1, textarea);
textboxBase = textarea;
}
else {
TextBox textbox = new TextBox();
textbox.setText(snippetParts.get(idxParams).getValue());
textbox.addStyleName(textboxStyle);
textboxBase = textbox;
}
connGrid.setWidget(idxRow, 1, textboxBase);
textboxBase.addChangeHandler(new ChangeHandler()
{
@Override
public void onChange(ChangeEvent arg0)
{
partsKeyValues_.put(key, textboxBase.getValue());
updateCodePanel();
}
});
if (idxRow == 0 && hasSecondaryHeaderField) {
idxParams++;
final String secondKey = snippetParts.get(idxParams).getKey();
Label secondLabel = new Label(secondKey + ":");
secondLabel.addStyleName(RES.styles().secondLabel());
connGrid.setWidget(idxRow, 2, secondLabel);
connGrid.getRowFormatter().setVerticalAlign(idxRow, HasVerticalAlignment.ALIGN_TOP);
final TextBox secondTextbox = new TextBox();
secondTextbox.setText(snippetParts.get(idxParams).getValue());
secondTextbox.addStyleName(RES.styles().secondTextbox());
connGrid.setWidget(idxRow, 3, secondTextbox);
connGrid.getCellFormatter().getElement(idxRow, 3).setAttribute("colspan", "2");
secondTextbox.addChangeHandler(new ChangeHandler()
{
@Override
public void onChange(ChangeEvent arg0)
{
partsKeyValues_.put(secondKey, secondTextbox.getValue());
updateCodePanel();
}
});
}
}
HorizontalPanel buttonsPanel = new HorizontalPanel();
buttonsPanel.addStyleName(RES.styles().buttonsPanel());
final ThemedButton testButton = new ThemedButton("Test");
testButton.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
testButton.setEnabled(false);
server_.connectionTest(
codePanel_.getCode(),
new DelayedProgressRequestCallback<String>("Testing Connection...") {
@Override
protected void onSuccess(String error)
{
testButton.setEnabled(true);
if (StringUtil.isNullOrEmpty(error)) {
showSuccess();
}
else {
showFailure(error);
}
}
@Override
public void onError(ServerError error) {
testButton.setEnabled(true);
}
});
}
});
buttonsPanel.add(testButton);
if (showAdvancedButton) {
ThemedButton optionsButton = new ThemedButton("Advanced Options...", new ClickHandler() {
public void onClick(ClickEvent event) {
new NewConnectionSnippetDialog(
new OperationWithInput<HashMap<String, String>>() {
@Override
public void execute(final HashMap<String, String> result)
{
for(String key : result.keySet()) {
partsKeyValues_.put(key, result.get(key));
}
updateCodePanel();
}
},
secondarySnippetParts,
info
).showModal();
}
});
buttonsPanel.add(optionsButton);
}
connGrid.getRowFormatter().setStyleName(visibleRows, RES.styles().lastRow());
connGrid.getCellFormatter().getElement(visibleRows, 1).setAttribute("colspan", "4");
connGrid.setWidget(visibleRows, 1, buttonsPanel);
return connGrid;
}
private void updateCodePanel()
{
String input = info_.getSnippet();
RegExp regExp = RegExp.compile(pattern_, "g");
StringBuilder builder = new StringBuilder();
int inputIndex = 0;
for (MatchResult matcher = regExp.exec(input); matcher != null; matcher = regExp.exec(input)) {
if (matcher.getGroupCount() >= 2) {
String key = matcher.getGroup(2);
String value = matcher.getGroupCount() >= 4 ? matcher.getGroup(4) : null;
String connStringField = matcher.getGroupCount() >= 6 ? matcher.getGroup(6) : null;
if (value != null) {
value = value.replaceAll("\\$colon\\$", ":");
value = value.replaceAll("\\$equal\\$", "=");
}
builder.append(input.substring(inputIndex, matcher.getIndex()));
if (partsKeyValues_.containsKey(key)) {
value = partsKeyValues_.get(key);
}
if (value != null) {
if (connStringField != null) {
builder.append(connStringField);
builder.append("=");
}
builder.append(value);
if (connStringField != null) {
builder.append(";");
}
}
inputIndex = matcher.getIndex() + matcher.getGroup(0).length();
}
}
builder.append(input.substring(inputIndex, input.length()));
codePanel_.setCode(builder.toString(), "");
}
private Widget createWidget()
{
VerticalPanel container = new VerticalPanel();
parametersPanel_ = new VerticalPanel();
parametersPanel_.addStyleName(RES.styles().parametersPanel());
container.add(parametersPanel_);
// add the code panel
codePanel_ = new ConnectionCodePanel();
codePanel_.addStyleName(RES.styles().dialogCodePanel());
Grid codeGrid = new Grid(1, 1);
codeGrid.addStyleName(RES.styles().codeGrid());
codeGrid.setCellPadding(0);
codeGrid.setCellSpacing(0);
codeGrid.setWidget(0, 0, codePanel_);
container.add(codeGrid);
return container;
}
public ConnectionOptions collectInput()
{
// collect the result
ConnectionOptions result = ConnectionOptions.create(
codePanel_.getCode(),
codePanel_.getConnectVia());
// return result
return result;
}
public interface Styles extends CssResource
{
String helpLink();
String codeGrid();
String dialogCodePanel();
String grid();
String gridRow();
String label();
String textbox();
String textarea();
String parametersPanel();
String firstTextbox();
String secondLabel();
String secondTextbox();
String buttonTextbox();
String settingsButton();
String lastRow();
String buttonsPanel();
String dialogMessagePanel();
}
public interface Resources extends ClientBundle
{
@Source("NewConnectionSnippetHost.css")
Styles styles();
}
public static Resources RES = GWT.create(Resources.class);
public static void ensureStylesInjected()
{
RES.styles().ensureInjected();
}
private ConnectionCodePanel codePanel_;
private VerticalPanel parametersPanel_;
@SuppressWarnings("unused")
private NewConnectionSnippetHostResources newConnectionSnippetHostResources_;
NewConnectionInfo info_;
ArrayList<NewConnectionSnippetParts> snippetParts_;
HashMap<String, String> partsKeyValues_ = new HashMap<String, String>();
static final String pattern_ = "\\$\\{([0-9]+):([^:=}]+)(=([^:}]*))?(:([^}]+))?\\}";
private ConnectionsServerOperations server_;
}