/******************************************************************************* * Copyright (c) 2012 - 2013 GoPivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * GoPivotal, Inc. - initial API and implementation *******************************************************************************/ package org.springframework.ide.eclipse.config.ui.editors.namespaces; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import org.eclipse.core.resources.IProject; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.dialogs.MessageDialogWithToggle; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.viewers.CheckStateChangedEvent; import org.eclipse.jface.viewers.CheckboxTableViewer; import org.eclipse.jface.viewers.ColumnViewer; import org.eclipse.jface.viewers.ICheckStateListener; import org.eclipse.jface.viewers.IStructuredContentProvider; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.wst.sse.ui.internal.StructuredTextViewer; import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument; import org.eclipse.wst.xml.core.internal.provisional.document.IDOMElement; import org.springframework.ide.eclipse.beans.core.BeansCorePlugin; import org.springframework.ide.eclipse.beans.core.model.INamespaceDefinitionListener; import org.springframework.ide.eclipse.beans.ui.namespaces.DefaultNamespaceDefinition; import org.springframework.ide.eclipse.beans.ui.namespaces.INamespaceDefinition; import org.springframework.ide.eclipse.beans.ui.namespaces.NamespaceUtils; import org.springframework.ide.eclipse.config.core.ConfigCoreUtils; import org.springframework.ide.eclipse.config.core.formatting.ShallowFormatProcessorXML; import org.springframework.ide.eclipse.config.core.preferences.SpringConfigPreferenceConstants; import org.springframework.ide.eclipse.config.core.schemas.BeansSchemaConstants; import org.springframework.ide.eclipse.config.ui.ConfigUiPlugin; import org.springframework.ide.eclipse.config.ui.editors.AbstractConfigFormPage; import org.springframework.ide.eclipse.config.ui.editors.AbstractConfigLabelProvider; import org.springframework.ide.eclipse.config.ui.editors.AbstractConfigMasterPart; import org.springframework.ide.eclipse.config.ui.editors.SpringConfigContentProvider; import org.springsource.ide.eclipse.commons.ui.StsUiImages; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; /** * @author Leo Dos Santos * @author Christian Dupuis */ @SuppressWarnings("restriction") public class NamespacesMasterPart extends AbstractConfigMasterPart implements INamespaceDefinitionListener { private class CheckStateListener implements ICheckStateListener { public void checkStateChanged(CheckStateChangedEvent event) { if (event.getElement() != null && event.getElement() instanceof INamespaceDefinition) { INamespaceDefinition definition = (INamespaceDefinition) event.getElement(); updateDefinitionFromCheckState(definition, event.getChecked()); } } } private class XsdConfigContentProvider extends SpringConfigContentProvider { public XsdConfigContentProvider(AbstractConfigFormPage page) { super(page); } @Override protected List<String> getChildNames(IDOMElement element) { return new ArrayList<String>(); } @Override public Object[] getElements(Object inputElement) { List<INamespaceDefinition> result = new ArrayList<INamespaceDefinition>(); if (inputElement instanceof Document) { Document document = (Document) inputElement; rootElement = document.getDocumentElement(); result = namespaceDefinitionList; } return result.toArray(); } } private Element rootElement; private volatile List<INamespaceDefinition> namespaceDefinitionList = new CopyOnWriteArrayList<INamespaceDefinition>(); private volatile boolean loading = false; private final Set<INamespaceDefinition> selectedNamespaces; private final Map<INamespaceDefinition, String> selectedVersions; private CheckboxTableViewer xsdViewer; private CheckStateListener checkListener; private final ShallowFormatProcessorXML formatProcessor; private final CountDownLatch lazyInitializationLatch; public NamespacesMasterPart(AbstractConfigFormPage page, Composite parent) { super(page, parent); selectedNamespaces = new HashSet<INamespaceDefinition>(); selectedVersions = new HashMap<INamespaceDefinition, String>(); formatProcessor = new ShallowFormatProcessorXML(); lazyInitializationLatch = new CountDownLatch(1); BeansCorePlugin.registerNamespaceDefinitionListener(this); getNamespaceDefinitionList(); } protected void addXsdDefinition(INamespaceDefinition definition) { if (!existsInConfiguration(definition)) { StructuredTextViewer textView = getConfigEditor().getTextViewer(); IDOMDocument doc = getConfigEditor().getDomDocument(); doc.getModel().beginRecording(textView); if (rootElement == null) { // Create a beans element and add the default namespace rootElement = doc.createElement(BeansSchemaConstants.ELEM_BEANS); doc.appendChild(rootElement); INamespaceDefinition defNamespace = NamespaceUtils.getDefaultNamespaceDefinition(); if (defNamespace != null) { if (!xsdViewer.getChecked(defNamespace)) { xsdViewer.setChecked(defNamespace, true); } rootElement.setAttribute(ConfigCoreUtils.ATTR_DEFAULT_NAMESPACE, defNamespace.getNamespaceURI()); } rootElement.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); //$NON-NLS-1$ //$NON-NLS-2$ } // Check again so we don't add the default namespace twice if (!existsInConfiguration(definition)) { rootElement.setAttribute( ConfigCoreUtils.ATTR_NAMESPACE_PREFIX + definition.getNamespacePrefix(getConfigEditor().getResourceFile()), definition.getNamespaceURI()); } selectedNamespaces.add(definition); updateXsdVersion(); doc.getModel().endRecording(textView); } } private String attributeNameToPrefix(String attributeName, String uri) { int colonIndex = attributeName.indexOf(":"); //$NON-NLS-1$ if (attributeName.length() > 6 && attributeName.indexOf(":") > 0) { //$NON-NLS-1$ return attributeName.substring(colonIndex + 1, attributeName.length()); } else if (attributeName.equals(ConfigCoreUtils.ATTR_DEFAULT_NAMESPACE)) { return uri.substring(uri.lastIndexOf("/") + 1); //$NON-NLS-1$ } else { return null; } } @Override protected void createButtons(Composite client) { // Ignore } @Override protected ColumnViewer createViewer(Composite client) { return CheckboxTableViewer.newCheckList(client, SWT.BORDER); } @Override protected SpringConfigContentProvider createViewerContentProvider() { return new XsdConfigContentProvider(getFormPage()); } @Override protected AbstractConfigLabelProvider createViewerLabelProvider() { return new NamespacesLabelProvider(getConfigEditor().getResourceFile()); } @Override public void dispose() { if (xsdViewer != null && checkListener != null) { xsdViewer.removeCheckStateListener(checkListener); } BeansCorePlugin.unregisterNamespaceDefinitionListener(this); } /** * Returns true if the given namespaceDefinition exists as an attribute in * the XML configuration file. */ private boolean existsInConfiguration(INamespaceDefinition namespaceDefinition) { if (rootElement != null) { NamedNodeMap attributeMap = rootElement.getAttributes(); for (int i = 0; i < attributeMap.getLength(); i++) { Node currItem = attributeMap.item(i); // Regular namespace case if (currItem.getNodeName().equals( ConfigCoreUtils.ATTR_NAMESPACE_PREFIX + namespaceDefinition.getNamespacePrefix(getConfigEditor().getResourceFile()))) { return true; } // Default namespace case else if (currItem.getNodeName().equals(ConfigCoreUtils.ATTR_DEFAULT_NAMESPACE) && currItem.getNodeValue().equals(namespaceDefinition.getNamespaceURI())) { return true; } } // Special case List<String> schemaLocationAttrs = ConfigCoreUtils.parseSchemaLocationAttr(getConfigEditor() .getDomDocument()); if (schemaLocationAttrs != null && schemaLocationAttrs.contains(namespaceDefinition.getNamespaceURI())) { return true; } } return false; } @Override protected void fillContextMenu(IMenuManager manager) { // TODO Auto-generated method stub } public CountDownLatch getLazyInitializationLatch() { return lazyInitializationLatch; } private synchronized List<INamespaceDefinition> getNamespaceDefinitionList() { if ((namespaceDefinitionList == null || namespaceDefinitionList.size() == 0) && !loading) { loading = true; if (getConfigEditor().getResourceFile() != null) { NamespaceUtils.getNamespaceDefinitions(getConfigEditor().getResourceFile().getProject(), new NamespaceUtils.INamespaceDefinitionTemplate() { public void doWithNamespaceDefinitions(INamespaceDefinition[] namespaceDefinitions, IProject project) { List<INamespaceDefinition> newNamespaceDefinitions = new ArrayList<INamespaceDefinition>( Arrays.asList(namespaceDefinitions)); NamespacesMasterPart.this.namespaceDefinitionList = triggerLoadNamespaceDefinitionList(newNamespaceDefinitions); Display.getDefault().asyncExec(new Runnable() { public void run() { if (getViewer().getControl() != null && !getViewer().getControl().isDisposed()) { getViewer().setInput(getConfigEditor().getDomDocument()); refresh(); } } }); loading = false; lazyInitializationLatch.countDown(); } }); } } return namespaceDefinitionList; } /** * Return the namespace definitions that are already declared in the * configuration file. */ private Object[] getPreselectedElements() { Set<INamespaceDefinition> checkedElements = new HashSet<INamespaceDefinition>(); Object[] availableNamespaces = ((IStructuredContentProvider) getViewer().getContentProvider()) .getElements(getConfigEditor().getDomDocument()); for (Object currAvailableNamespace : availableNamespaces) { INamespaceDefinition currNamespaceDefinition = (INamespaceDefinition) currAvailableNamespace; if (existsInConfiguration(currNamespaceDefinition)) { checkedElements.add(currNamespaceDefinition); selectedNamespaces.add(currNamespaceDefinition); String existingVersion = ConfigCoreUtils.getSelectedSchemaLocation(getConfigEditor().getDomDocument(), currNamespaceDefinition.getNamespaceURI()); if (!"".equals(existingVersion)) { //$NON-NLS-1$ selectedVersions.put(currNamespaceDefinition, existingVersion); } } } return checkedElements.toArray(); } private String getSchemaLocationValue(INamespaceDefinition namespaceDefinition, Map<INamespaceDefinition, String> schemaVersions) { String schemaVersion = schemaVersions.get(namespaceDefinition); if (schemaVersion == null) { schemaVersion = namespaceDefinition.getDefaultSchemaLocation(getConfigEditor().getResourceFile()); } return namespaceDefinition.getNamespaceURI() + " " + schemaVersion; //$NON-NLS-1$ } protected Map<INamespaceDefinition, String> getSchemaVersions() { return selectedVersions; } @Override protected String getSectionDescription() { return Messages.getString("NamespacesMasterPart.MASTER_SECTION_DESCRIPTION"); //$NON-NLS-1$ } @Override protected String getSectionTitle() { return Messages.getString("NamespacesMasterPart.MASTER_SECTION_TITLE"); //$NON-NLS-1$ } private boolean isUnknownNamespace(String attributeName, String namespaceUri, List<INamespaceDefinition> namespaceDefinitionList) { // Ignore xsi if (attributeName.toLowerCase().startsWith("xmlns:xsi")) { //$NON-NLS-1$ return false; } // Check non-default namespace case if (attributeName.toLowerCase().startsWith(ConfigCoreUtils.ATTR_NAMESPACE_PREFIX) && !namespaceAttributeExistsInList(attributeName, namespaceUri, namespaceDefinitionList) && attributeNameToPrefix(attributeName, namespaceUri) != null) { return true; } // Check default namespace case if (attributeName.equalsIgnoreCase(ConfigCoreUtils.ATTR_DEFAULT_NAMESPACE) && !namespaceAttributeExistsInList(attributeName, namespaceUri, namespaceDefinitionList)) { return true; } return false; } private boolean namespaceAttributeExistsInList(String attributeName, String namespaceUri, List<INamespaceDefinition> namespaces) { String namespacePrefix = attributeNameToPrefix(attributeName, namespaceUri); for (INamespaceDefinition namespaceDefinition : namespaces) { INamespaceDefinition currNamespaceDefinition = namespaceDefinition; if ((namespacePrefix != null && namespacePrefix.equals(currNamespaceDefinition .getNamespacePrefix(getConfigEditor().getResourceFile()))) || namespaceUri.equalsIgnoreCase(currNamespaceDefinition.getNamespaceURI())) { return true; } } return false; } public void onNamespaceDefinitionRegistered(INamespaceDefinitionListener.NamespaceDefinitionChangeEvent event) { if (shouldRefresh(event)) { namespaceDefinitionList.clear(); getNamespaceDefinitionList(); } } public void onNamespaceDefinitionUnregistered(INamespaceDefinitionListener.NamespaceDefinitionChangeEvent event) { onNamespaceDefinitionRegistered(event); } private void openPontDialog() { IPreferenceStore prefStore = ConfigUiPlugin.getDefault().getPreferenceStore(); if (prefStore.getString(SpringConfigPreferenceConstants.PREF_DISPLAY_TABS_DIALOG).equals( MessageDialogWithToggle.PROMPT)) { MessageDialogWithToggle .openInformation( getFormPage().getSite().getShell(), Messages.getString("NamespacesMasterPart.PONT_DIALOG_TITLE"), //$NON-NLS-1$ Messages.getString("NamespacesMasterPart.PONT_DIALOG_MESSAGE"), //$NON-NLS-1$ Messages.getString("NamespacesMasterPart.PONT_DIALOG_CHECKBOX"), false, prefStore, SpringConfigPreferenceConstants.PREF_DISPLAY_TABS_DIALOG); //$NON-NLS-1$ } } @Override protected void postCreateContents() { ColumnViewer viewer = getViewer(); if (viewer != null && viewer instanceof CheckboxTableViewer) { checkListener = new CheckStateListener(); xsdViewer = (CheckboxTableViewer) viewer; xsdViewer.setCheckedElements(getPreselectedElements()); xsdViewer.addCheckStateListener(checkListener); } } @Override public void refresh() { xsdViewer.setCheckedElements(getPreselectedElements()); super.refresh(); } protected void removeXsdDefinition(INamespaceDefinition definition) { if (existsInConfiguration(definition)) { StructuredTextViewer textView = getConfigEditor().getTextViewer(); IDOMDocument doc = getConfigEditor().getDomDocument(); doc.getModel().beginRecording(textView); rootElement.removeAttribute(ConfigCoreUtils.ATTR_NAMESPACE_PREFIX + definition.getNamespacePrefix(getConfigEditor().getResourceFile())); Attr attr = rootElement.getAttributeNode(ConfigCoreUtils.ATTR_DEFAULT_NAMESPACE); if (attr != null && attr.getNodeValue().equals(definition.getNamespaceURI())) { rootElement.removeAttributeNode(attr); } selectedNamespaces.remove(definition); updateXsdVersion(); doc.getModel().endRecording(textView); } } private boolean shouldRefresh(INamespaceDefinitionListener.NamespaceDefinitionChangeEvent event) { return event.getProject() == null || event.getProject().equals(getConfigEditor().getResourceFile().getProject()); } private List<INamespaceDefinition> triggerLoadNamespaceDefinitionList( List<INamespaceDefinition> namespaceDefinitionList) { // Add any namespaces that exist in the XML document but aren't // already known to the tooling via the extension point if (rootElement == null) { rootElement = getConfigEditor().getDomDocument().getDocumentElement(); } if (rootElement != null) { NamedNodeMap beanAttributes = rootElement.getAttributes(); for (int i = 0; i < beanAttributes.getLength(); i++) { Node currAttributeNode = beanAttributes.item(i); String currAttributeName = currAttributeNode.getNodeName(); String uri = currAttributeNode.getNodeValue(); if (isUnknownNamespace(currAttributeName, uri, namespaceDefinitionList)) { String schemaVersion = ConfigCoreUtils.getSelectedSchemaLocation( getConfigEditor().getDomDocument(), uri); INamespaceDefinition namespaceDefinition = new DefaultNamespaceDefinition(attributeNameToPrefix( currAttributeName, uri), uri, schemaVersion, StsUiImages.XML_FILE.createImage()); if (!"".equals(schemaVersion)) { //$NON-NLS-1$ this.selectedVersions.put(namespaceDefinition, schemaVersion); } namespaceDefinitionList.add(namespaceDefinition); } } } // Special case List<String> schemaInfo = ConfigCoreUtils.parseSchemaLocationAttr(getConfigEditor().getDomDocument()); if (schemaInfo != null) { Iterator<String> iter = schemaInfo.iterator(); while (iter.hasNext()) { String uri = iter.next(); if (iter.hasNext()) { String schemaVersion = iter.next(); if (!namespaceAttributeExistsInList(ConfigCoreUtils.ATTR_SCHEMA_LOCATION, uri, namespaceDefinitionList)) { INamespaceDefinition namespaceDefinition = new DefaultNamespaceDefinition( attributeNameToPrefix(ConfigCoreUtils.ATTR_SCHEMA_LOCATION, uri), uri, schemaVersion, StsUiImages.XML_FILE.createImage()); if (!"".equals(schemaVersion)) { //$NON-NLS-1$ this.selectedVersions.put(namespaceDefinition, schemaVersion); } namespaceDefinitionList.add(namespaceDefinition); } } } } return namespaceDefinitionList; } public void updateDefinitionFromCheckState(INamespaceDefinition definition, boolean checked) { if (checked) { addXsdDefinition(definition); } else { removeXsdDefinition(definition); } openPontDialog(); } protected void updateXsdVersion() { rootElement.removeAttribute(ConfigCoreUtils.ATTR_SCHEMA_LOCATION); Set<INamespaceDefinition> namespaces = selectedNamespaces; Map<INamespaceDefinition, String> schemaVersions = getSchemaVersions(); String schemaLocationAttrVal = null; for (INamespaceDefinition currNamespaceDefinition : namespaces) { if (currNamespaceDefinition.getDefaultSchemaLocation(getConfigEditor().getResourceFile()) != null) { String currNamespaceDefSchemaLocationVal = getSchemaLocationValue(currNamespaceDefinition, schemaVersions); // Append the new schema location if (schemaLocationAttrVal == null) { schemaLocationAttrVal = currNamespaceDefSchemaLocationVal; } else { schemaLocationAttrVal += "\n" + "\t\t" + currNamespaceDefSchemaLocationVal; //$NON-NLS-1$ //$NON-NLS-2$ } } } if (schemaLocationAttrVal != null) { rootElement.setAttribute(ConfigCoreUtils.ATTR_SCHEMA_LOCATION, schemaLocationAttrVal.trim()); } formatProcessor.formatNode(rootElement); } }