/* * SoapUI, Copyright (C) 2004-2016 SmartBear Software * * Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent * versions of the EUPL (the "Licence"); * You may not use this work except in compliance with the Licence. * You may obtain a copy of the Licence at: * * http://ec.europa.eu/idabc/eupl * * Unless required by applicable law or agreed to in writing, software distributed under the Licence is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the Licence for the specific language governing permissions and limitations * under the Licence. */ package com.eviware.soapui.support.editor.views.xml.source; import com.eviware.soapui.SoapUI; import com.eviware.soapui.impl.wsdl.submit.transports.http.DocumentContent; import com.eviware.soapui.model.ModelItem; import com.eviware.soapui.model.propertyexpansion.PropertyExpander; import com.eviware.soapui.settings.UISettings; import com.eviware.soapui.support.DocumentListenerAdapter; import com.eviware.soapui.support.UISupport; import com.eviware.soapui.support.components.JEditorStatusBar.JEditorStatusBarTarget; import com.eviware.soapui.support.components.PreviewCorner; import com.eviware.soapui.support.editor.EditorDocument; import com.eviware.soapui.support.editor.EditorLocation; import com.eviware.soapui.support.editor.views.AbstractXmlEditorView; import com.eviware.soapui.support.editor.xml.XmlDocument; import com.eviware.soapui.support.editor.xml.XmlEditor; import com.eviware.soapui.support.editor.xml.XmlLocation; import com.eviware.soapui.support.editor.xml.support.ValidationError; import com.eviware.soapui.support.propertyexpansion.PropertyExpansionPopupListener; import com.eviware.soapui.support.swing.JTextComponentPopupMenu; import com.eviware.soapui.support.swing.SoapUISplitPaneUI; import com.eviware.soapui.support.xml.XmlUtils; import com.eviware.soapui.support.xml.actions.EnableLineNumbersAction; import com.eviware.soapui.support.xml.actions.FormatXmlAction; import com.eviware.soapui.support.xml.actions.GoToLineAction; import com.eviware.soapui.support.xml.actions.InsertBase64FileTextAreaAction; import com.eviware.soapui.support.xml.actions.LoadXmlTextAreaAction; import com.eviware.soapui.support.xml.actions.SaveXmlTextAreaAction; import com.jgoodies.forms.builder.ButtonBarBuilder; import org.apache.xmlbeans.XmlError; import org.apache.xmlbeans.XmlException; import org.apache.xmlbeans.XmlOptions; import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; import org.fife.ui.rsyntaxtextarea.SyntaxConstants; import org.fife.ui.rsyntaxtextarea.Theme; import org.fife.ui.rtextarea.RTextScrollPane; import org.fife.ui.rtextarea.SearchContext; import org.fife.ui.rtextarea.SearchEngine; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.ButtonGroup; import javax.swing.DefaultListModel; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JRadioButton; import javax.swing.JScrollPane; import javax.swing.JSeparator; import javax.swing.JSplitPane; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import javax.swing.event.CaretListener; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.GridLayout; import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.IOException; import java.util.ArrayList; import java.util.List; import static com.eviware.soapui.support.JsonUtil.seemsToBeJsonContentType; /** * Default "XML" source editor view in SoapUI * * @author ole.matzura */ public class XmlSourceEditorView<T extends ModelItem> extends AbstractXmlEditorView<XmlDocument> { private static final String RSYNTAXAREA_THEME = "/rsyntaxarea-theme/soapui.xml"; private RSyntaxTextArea editArea; private RTextScrollPane editorScrollPane; private ValidateMessageXmlAction validateXmlAction; private JSplitPane splitter; private JScrollPane errorScrollPane; private DefaultListModel errorListModel; private boolean updating; public boolean isLocating; private JPopupMenu inputPopup; private PreviewCorner previewCorner; private T modelItem; private EnableLineNumbersAction enableLineNumbersAction; private GoToLineAction goToLineAction; private SaveXmlTextAreaAction saveXmlTextAreaAction; // Read only views don't need these private FormatXmlAction formatXmlAction; private LoadXmlTextAreaAction loadXmlTextAreaAction; private InsertBase64FileTextAreaAction insertBase64FileTextAreaAction; private FindAndReplaceDialogView findAndReplaceDialog; private final boolean readOnly; public XmlSourceEditorView(XmlEditor<XmlDocument> xmlEditor, T modelItem, boolean readOnly) { this(xmlEditor, modelItem, readOnly, "XML"); } public XmlSourceEditorView(XmlEditor<XmlDocument> xmlEditor, T modelItem, boolean readOnly, String tabTitle) { super(tabTitle, xmlEditor, XmlSourceEditorViewFactory.VIEW_ID); this.modelItem = modelItem; this.readOnly = readOnly; } protected void buildUI() { editArea = new RSyntaxTextArea(20, 60); try { Theme theme = Theme.load(XmlSourceEditorView.class.getResourceAsStream(RSYNTAXAREA_THEME)); theme.apply(editArea); } catch (IOException e) { SoapUI.logError(e, "Could not load XML editor color theme file"); } editArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_XML); editArea.setFont(UISupport.getEditorFont()); editArea.setCodeFoldingEnabled(true); editArea.setAntiAliasingEnabled(true); editArea.setMinimumSize(new Dimension(50, 50)); editArea.setCaretPosition(0); editArea.setEnabled(!readOnly); editArea.setEditable(!readOnly); editArea.setBorder(BorderFactory.createMatteBorder(0, 2, 0, 0, Color.WHITE)); errorListModel = new DefaultListModel(); JList list = new JList(errorListModel); list.addMouseListener(new ValidationListMouseAdapter(list, editArea)); errorScrollPane = new JScrollPane(list); errorScrollPane.setVisible(false); splitter = new JSplitPane(JSplitPane.VERTICAL_SPLIT) { public void requestFocus() { SwingUtilities.invokeLater(new Runnable() { public void run() { editArea.requestFocusInWindow(); } }); } public boolean hasFocus() { return editArea.hasFocus(); } }; splitter.setUI(new SoapUISplitPaneUI()); splitter.setDividerSize(0); splitter.setOneTouchExpandable(true); editArea.getDocument().addDocumentListener(new DocumentListenerAdapter() { public void update(Document document) { if (!updating && getDocument() != null) { updating = true; getDocument().setDocumentContent(getDocument().getDocumentContent(EditorDocument.Format.XML).withContent(editArea.getText())); updating = false; } } }); JPanel p = new JPanel(new BorderLayout()); editorScrollPane = new RTextScrollPane(editArea); JTextComponentPopupMenu.add(editArea); buildPopup(editArea.getPopupMenu(), editArea); if (UISupport.isMac()) { editArea.getInputMap().put(KeyStroke.getKeyStroke("shift meta V"), validateXmlAction); editArea.getInputMap().put(KeyStroke.getKeyStroke("meta S"), saveXmlTextAreaAction); editArea.getInputMap().put(KeyStroke.getKeyStroke("control L"), enableLineNumbersAction); editArea.getInputMap().put(KeyStroke.getKeyStroke("control G"), goToLineAction); editArea.getInputMap().put(KeyStroke.getKeyStroke("meta F"), findAndReplaceDialog); editArea.getInputMap().put(KeyStroke.getKeyStroke("shift meta F"), formatXmlAction); if (!readOnly) { editArea.getInputMap().put(KeyStroke.getKeyStroke("meta L"), loadXmlTextAreaAction); } } else { editArea.getInputMap().put(KeyStroke.getKeyStroke("alt V"), validateXmlAction); editArea.getInputMap().put(KeyStroke.getKeyStroke("ctrl S"), saveXmlTextAreaAction); editArea.getInputMap().put(KeyStroke.getKeyStroke("alt L"), enableLineNumbersAction); editArea.getInputMap().put(KeyStroke.getKeyStroke("control G"), goToLineAction); editArea.getInputMap().put(KeyStroke.getKeyStroke("ctrl F"), findAndReplaceDialog); editArea.getInputMap().put(KeyStroke.getKeyStroke("alt F"), formatXmlAction); if (!readOnly) { editArea.getInputMap().put(KeyStroke.getKeyStroke("ctrl L"), loadXmlTextAreaAction); } } editorScrollPane.setLineNumbersEnabled(SoapUI.getSettings().getBoolean(UISettings.SHOW_XML_LINE_NUMBERS)); editorScrollPane.setFoldIndicatorEnabled(true); p.add(editorScrollPane, BorderLayout.CENTER); splitter.setTopComponent(editorScrollPane); splitter.setBottomComponent(errorScrollPane); splitter.setDividerLocation(1.0); splitter.setBorder(null); previewCorner = UISupport.addPreviewCorner(getEditorScrollPane(), true); if (!readOnly) { PropertyExpansionPopupListener.enable(editArea, modelItem); } } public JScrollPane getEditorScrollPane() { return editorScrollPane; } public T getModelItem() { return modelItem; } protected void buildPopup(JPopupMenu inputPopup, RSyntaxTextArea editArea) { this.inputPopup = inputPopup; validateXmlAction = new ValidateMessageXmlAction(); saveXmlTextAreaAction = new SaveXmlTextAreaAction(editArea, "Save"); enableLineNumbersAction = new EnableLineNumbersAction(editorScrollPane, "Toggle Line Numbers"); goToLineAction = new GoToLineAction(editArea, "Go To Line"); findAndReplaceDialog = new FindAndReplaceDialogView("Find / Replace"); if (!readOnly) { loadXmlTextAreaAction = new LoadXmlTextAreaAction(editArea, "Load"); insertBase64FileTextAreaAction = new InsertBase64FileTextAreaAction(editArea, "Insert File as Base64"); } int cnt = inputPopup.getComponentCount(); for (int i = cnt - 1; i >= 0; i--) { if (inputPopup.getComponent(i) instanceof JSeparator) { inputPopup.remove(inputPopup.getComponent(i)); } } inputPopup.insert(validateXmlAction, 0); formatXmlAction = new FormatXmlAction(editArea); inputPopup.insert(formatXmlAction, 1); inputPopup.addSeparator(); inputPopup.add(findAndReplaceDialog); inputPopup.addSeparator(); inputPopup.add(goToLineAction); inputPopup.add(enableLineNumbersAction); inputPopup.addSeparator(); inputPopup.add(saveXmlTextAreaAction); if (!readOnly) { inputPopup.add(loadXmlTextAreaAction); inputPopup.add(insertBase64FileTextAreaAction); } } @Override public void release() { super.release(); inputPopup.removeAll(); previewCorner.release(); modelItem = null; } private final class FindAndReplaceDialogView extends AbstractAction { private JDialog dialog; private JCheckBox caseCheck; private JRadioButton allButton; private JRadioButton selectedLinesButton; private JRadioButton forwardButton; private JRadioButton backwardButton; private JCheckBox wholeWordCheck; private JButton findButton; private JButton replaceButton; private JButton replaceAllButton; private JComboBox findCombo; private JComboBox replaceCombo; private final String title; public FindAndReplaceDialogView(String title) { super(title); this.title = title; if (UISupport.isMac()) { putValue(Action.ACCELERATOR_KEY, UISupport.getKeyStroke("meta F")); } else { putValue(Action.ACCELERATOR_KEY, UISupport.getKeyStroke("ctrl F")); } } @Override public void actionPerformed(ActionEvent arg0) { show(); } public void show() { if (dialog == null) { buildDialog(); } editArea.requestFocusInWindow(); replaceCombo.setEnabled(!readOnly); replaceAllButton.setEnabled(!readOnly); replaceButton.setEnabled(!readOnly); UISupport.showDialog(dialog); findCombo.getEditor().selectAll(); findCombo.requestFocus(); } private void buildDialog() { Window window = SwingUtilities.windowForComponent(editArea); dialog = new JDialog(window, title); dialog.setModal(false); JPanel panel = new JPanel(new BorderLayout()); findCombo = new JComboBox(); findCombo.setEditable(true); replaceCombo = new JComboBox(); replaceCombo.setEditable(!readOnly); // create inputs GridLayout gridLayout = new GridLayout(2, 2); gridLayout.setVgap(5); JPanel inputPanel = new JPanel(gridLayout); inputPanel.add(new JLabel("Find:")); inputPanel.add(findCombo); inputPanel.add(new JLabel("Replace with:")); inputPanel.add(replaceCombo); inputPanel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8)); // create direction panel ButtonGroup directionGroup = new ButtonGroup(); forwardButton = new JRadioButton("Forward", true); forwardButton.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); directionGroup.add(forwardButton); backwardButton = new JRadioButton("Backward"); backwardButton.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); directionGroup.add(backwardButton); JPanel directionPanel = new JPanel(new GridLayout(2, 1)); directionPanel.add(forwardButton); directionPanel.add(backwardButton); directionPanel.setBorder(BorderFactory.createTitledBorder("Direction")); // create scope panel ButtonGroup scopeGroup = new ButtonGroup(); allButton = new JRadioButton("All", true); allButton.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); selectedLinesButton = new JRadioButton("Selected Lines"); selectedLinesButton.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); scopeGroup.add(allButton); scopeGroup.add(selectedLinesButton); JPanel scopePanel = new JPanel(new GridLayout(2, 1)); scopePanel.add(allButton); scopePanel.add(selectedLinesButton); scopePanel.setBorder(BorderFactory.createTitledBorder("Scope")); // create options caseCheck = new JCheckBox("Case Sensitive"); caseCheck.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); wholeWordCheck = new JCheckBox("Whole Word"); wholeWordCheck.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); JPanel optionsPanel = new JPanel(new GridLayout(2, 1)); optionsPanel.add(caseCheck); optionsPanel.add(wholeWordCheck); optionsPanel.setBorder(BorderFactory.createTitledBorder("Options")); // create panel with options JPanel options = new JPanel(new GridLayout(1, 2)); JPanel radios = new JPanel(new GridLayout(2, 1)); radios.add(directionPanel); radios.add(scopePanel); options.add(optionsPanel); options.add(radios); options.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 8)); // create buttons ButtonBarBuilder builder = new ButtonBarBuilder(); findButton = new JButton(new FindAction(findCombo)); builder.addFixed(findButton); builder.addRelatedGap(); replaceButton = new JButton(new ReplaceAction()); builder.addFixed(replaceButton); builder.addRelatedGap(); replaceAllButton = new JButton(new ReplaceAllAction()); builder.addFixed(replaceAllButton); builder.addUnrelatedGap(); builder.addFixed(new JButton(new CloseAction(dialog))); builder.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8)); // tie it up! panel.add(inputPanel, BorderLayout.NORTH); panel.add(options, BorderLayout.CENTER); panel.add(builder.getPanel(), BorderLayout.SOUTH); dialog.getContentPane().add(panel); dialog.pack(); UISupport.initDialogActions(dialog, null, findButton); } protected SearchContext createSearchAndReplaceContext() { if (findCombo.getSelectedItem() == null) { return null; } if (replaceCombo.getSelectedItem() == null) { return null; } String searchExpression = findCombo.getSelectedItem().toString(); String replacement = replaceCombo.getSelectedItem().toString(); SearchContext context = new SearchContext(); context.setSearchFor(searchExpression); context.setReplaceWith(replacement); context.setRegularExpression(false); context.setMatchCase(caseCheck.isSelected()); context.setSearchForward(forwardButton.isSelected()); context.setWholeWord(wholeWordCheck.isSelected()); return context; } protected SearchContext createSearchContext() { if (findCombo.getSelectedItem() == null) { return null; } String searchExpression = findCombo.getSelectedItem().toString(); SearchContext context = new SearchContext(); context.setSearchFor(searchExpression); context.setRegularExpression(false); context.setSearchForward(forwardButton.isSelected()); context.setMatchCase(caseCheck.isSelected()); context.setWholeWord(false); return context; } private class FindAction extends AbstractAction { public FindAction(JComboBox findCombo) { super("Find/Find Next"); } @Override public void actionPerformed(ActionEvent e) { SearchContext context = createSearchContext(); if (context == null) { return; } boolean found = SearchEngine.find(editArea, context); if (!found) { UISupport.showErrorMessage("String [" + context.getSearchFor() + "] not found"); } } } private class ReplaceAction extends AbstractAction { public ReplaceAction() { super("Replace/Replace Next"); } @Override public void actionPerformed(ActionEvent e) { SearchContext context = createSearchAndReplaceContext(); if (context == null) { return; } boolean found = SearchEngine.replace(editArea, context); if (!found) { UISupport.showErrorMessage("String [" + context.getSearchFor() + "] not found"); } } } private class ReplaceAllAction extends AbstractAction { public ReplaceAllAction() { super("Replace All"); } @Override public void actionPerformed(ActionEvent e) { SearchContext context = createSearchAndReplaceContext(); if (context == null) { return; } int replaceCount = SearchEngine.replaceAll(editArea, context); if (replaceCount <= 0) { UISupport.showErrorMessage("String [" + context.getSearchFor() + "] not found"); } } } private class CloseAction extends AbstractAction { final JDialog dialog; public CloseAction(JDialog d) { super("Close"); dialog = d; } public void actionPerformed(ActionEvent e) { dialog.setVisible(false); } } } private final static class ValidationListMouseAdapter extends MouseAdapter { private final JList list; private final RSyntaxTextArea textArea; public ValidationListMouseAdapter(JList list, RSyntaxTextArea editArea) { this.list = list; this.textArea = editArea; } public void mouseClicked(MouseEvent e) { if (e.getClickCount() < 2) { return; } int ix = list.getSelectedIndex(); if (ix == -1) { return; } Object obj = list.getModel().getElementAt(ix); if (obj instanceof ValidationError) { ValidationError error = (ValidationError) obj; if (error.getLineNumber() >= 0) { try { textArea.setCaretPosition(textArea.getLineStartOffset(error.getLineNumber() - 1)); } catch (BadLocationException e1) { SoapUI.logError(e1, "Unable to set the caret position. This is most likely a bug."); } textArea.requestFocus(); } else { Toolkit.getDefaultToolkit().beep(); } } else { Toolkit.getDefaultToolkit().beep(); } } } public RSyntaxTextArea getInputArea() { getComponent(); return editArea; } public static class JEditorStatusBarTargetProxy implements JEditorStatusBarTarget { private final RSyntaxTextArea textArea; public JEditorStatusBarTargetProxy(RSyntaxTextArea area) { textArea = area; } @Override public void addCaretListener(CaretListener listener) { textArea.addCaretListener(listener); } @Override public int getCaretPosition() { return textArea.getCaretPosition(); } @Override public void removeCaretListener(CaretListener listener) { textArea.removeCaretListener(listener); } @Override public int getLineStartOffset(int line) throws Exception { return textArea.getLineStartOffset(line); } @Override public int getLineOfOffset(int offset) throws Exception { return textArea.getLineOfOffset(offset); } } public void setEditable(boolean enabled) { getComponent(); editArea.setEditable(enabled); } protected ValidationError[] validateXml(String xml) { try { XmlUtils.createXmlObject(xml, new XmlOptions().setLoadLineNumbers()); } catch (XmlException e) { List<ValidationError> result = new ArrayList<ValidationError>(); if (e.getErrors() != null) { for (Object error : e.getErrors()) { if (error instanceof XmlError) { result.add(new com.eviware.soapui.model.testsuite.AssertionError((XmlError) error)); } else { result.add(new com.eviware.soapui.model.testsuite.AssertionError(error.toString())); } } } if (result.isEmpty()) { result.add(new com.eviware.soapui.model.testsuite.AssertionError(e.toString())); } return result.toArray(new ValidationError[result.size()]); } return null; } public class ValidateMessageXmlAction extends AbstractAction { public ValidateMessageXmlAction() { super("Validate"); if (UISupport.isMac()) { putValue(Action.ACCELERATOR_KEY, UISupport.getKeyStroke("shift meta V")); } else { putValue(Action.ACCELERATOR_KEY, UISupport.getKeyStroke("alt V")); } } public void actionPerformed(ActionEvent e) { if (validate()) { UISupport.showInfoMessage("Validation OK"); } } } public boolean activate(XmlLocation location) { super.activate(location); if (location != null) { setLocation(location); } editArea.requestFocus(); return true; } public JComponent getComponent() { if (splitter == null) { buildUI(); } return splitter; } public XmlLocation getEditorLocation() { return new XmlLocation(getCurrentLine() + 1, getCurrentColumn()); } @Override public void setLocation(EditorLocation<XmlDocument> location) { int line = location.getLine() - 1; if (line >= 0) { try { int caretLine = editArea.getCaretLineNumber(); int offset = editArea.getLineStartOffset(line); editArea.setCaretPosition(offset + location.getColumn()); int scrollLine = line + (line > caretLine ? 3 : -3); if (scrollLine >= editArea.getLineCount()) { scrollLine = editArea.getLineCount() - 1; } else if (scrollLine < 0) { scrollLine = 0; } editArea.scrollRectToVisible(new Rectangle(scrollLine, location.getColumn())); } catch (RuntimeException ignore) { } catch (BadLocationException e) { SoapUI.logError(e, "Unable to set the location in the XML document."); } } } public int getCurrentLine() { if (editArea == null) { return -1; } return editArea.getCaretLineNumber(); } public int getCurrentColumn() { if (editArea == null) { return -1; } try { int pos = editArea.getCaretPosition(); int line = editArea.getLineOfOffset(pos); return pos - editArea.getLineStartOffset(line); } catch (BadLocationException e) { SoapUI.logError(e, "Unable to get the current column. "); return -1; } } public String getText() { if (editArea == null) { return null; } return editArea.getText(); } public boolean validate() { ValidationError[] errors = validateXml(PropertyExpander.expandProperties(getModelItem(), editArea.getText())); errorListModel.clear(); if (errors == null || errors.length == 0) { splitter.setDividerLocation(1.0); splitter.setDividerSize(0); errorScrollPane.setVisible(false); return true; } else { Toolkit.getDefaultToolkit().beep(); for (ValidationError error : errors) { errorListModel.addElement(error); } errorScrollPane.setVisible(true); splitter.setDividerLocation(0.8); splitter.setDividerSize(10); return false; } } @Override public void documentUpdated() { if (!updating) { updating = true; final DocumentContent rawDocumentContent = getDocument().getDocumentContent(EditorDocument.Format.RAW); final String contentType = rawDocumentContent.getContentType(); if (rawDocumentContent.getContentAsString() == null) { editArea.setText(""); editArea.setEnabled(false); } else if (seemsToBeJsonContentType(contentType) && readOnly) { editArea.setText("The content you are trying to view cannot be viewed as XML"); editArea.setEnabled(false); } else { int caretPosition = editArea.getCaretPosition(); editArea.setEnabled(true); final String contentAsString = getDocument().getDocumentContent(EditorDocument.Format.XML).getContentAsString(); editArea.setText(contentAsString); editArea.setCaretPosition(caretPosition < contentAsString.length() ? caretPosition : 0); } updating = false; } } public boolean saveDocument(boolean validate) { return validate ? validate() : true; } public void locationChanged(EditorLocation<XmlDocument> location) { isLocating = true; setLocation(location); isLocating = false; } public JPopupMenu getEditorPopup() { return editArea.getPopupMenu(); } public boolean hasFocus() { return editArea.hasFocus(); } public boolean isInspectable() { return true; } public ValidateMessageXmlAction getValidateXmlAction() { return validateXmlAction; } }