/****************************************************************************** * Copyright (c) 2016 Oracle and Accenture * 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: * Konstantin Komissarchik - initial implementation * Kamesh Sampath - [355457] Improve DTD doctype specification in XML binding * Kamesh Sampath - [355751] General improvement of XML root binding API ******************************************************************************/ package org.eclipse.sapphire.modeling.xml; import static org.eclipse.sapphire.modeling.xml.XmlUtil.createDefaultElementName; import java.util.HashMap; import java.util.Locale; import java.util.Map; import javax.xml.namespace.QName; import org.eclipse.sapphire.Element; import org.eclipse.sapphire.ElementType; import org.eclipse.sapphire.LoggingService; import org.eclipse.sapphire.Sapphire; import org.eclipse.sapphire.modeling.CorruptedResourceException; import org.eclipse.sapphire.modeling.ResourceStoreException; import org.eclipse.sapphire.modeling.localization.LocalizationService; import org.eclipse.sapphire.modeling.xml.annotations.CustomXmlRootBinding; import org.eclipse.sapphire.modeling.xml.annotations.XmlBinding; import org.eclipse.sapphire.modeling.xml.annotations.XmlDocumentType; import org.eclipse.sapphire.modeling.xml.annotations.XmlSchema; import org.eclipse.sapphire.modeling.xml.annotations.XmlSchemas; import org.eclipse.sapphire.modeling.xml.internal.DocumentTypeRootElementController; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.ProcessingInstruction; import org.w3c.dom.Text; /** * @author <a href="mailto:konstantin.komissarchik@oracle.com">Konstantin Komissarchik</a> * @author <a href="mailto:kamesh.sampath@accenture.com">Kamesh Sampath</a> */ public class RootXmlResource extends XmlResource { private static final String PI_XML_TARGET = "xml"; private static final String PI_XML_DATA = "version=\"1.0\" encoding=\"UTF-8\""; private final XmlResourceStore store; private final Document document; private RootElementController rootElementController; private XmlElement rootXmlElement; public RootXmlResource() { this( new XmlResourceStore() ); } public RootXmlResource( final XmlResourceStore store ) { super( null ); this.store = store; this.document = store.getDomDocument(); } public XmlResourceStore store() { return this.store; } @Override public void init( final Element modelElement ) { super.init( modelElement ); final ElementType modelElementType = modelElement.type(); final CustomXmlRootBinding customXmlRootBindingAnnotation = modelElementType.getAnnotation( CustomXmlRootBinding.class ); if( customXmlRootBindingAnnotation != null ) { try { this.rootElementController = customXmlRootBindingAnnotation.value().newInstance(); } catch( Exception e ) { Sapphire.service( LoggingService.class ).log( e ); } } if( this.rootElementController == null ) { final XmlBinding xmlBindingAnnotation = modelElementType.getAnnotation( XmlBinding.class ); if( xmlBindingAnnotation != null && xmlBindingAnnotation.path().length() != 0 ) { final XmlPath path = new XmlPath( xmlBindingAnnotation.path(), getXmlNamespaceResolver() ); final QName qualifiedName = path.getSegment( 0 ).getQualifiedName(); final String localName = qualifiedName.getLocalPart(); final String prefix = qualifiedName.getPrefix(); final String namespace = qualifiedName.getNamespaceURI(); final XmlDocumentType xmlDocumentTypeAnnotation = modelElementType.getAnnotation( XmlDocumentType.class ); if( xmlDocumentTypeAnnotation != null && xmlDocumentTypeAnnotation.systemId().length() != 0 ) { this.rootElementController = new DocumentTypeRootElementController( localName ); } else { final Map<String,String> schemas = new HashMap<String,String>(); final XmlSchemas xmlSchemasAnnotation = modelElementType.getAnnotation( XmlSchemas.class ); if( xmlSchemasAnnotation != null ) { for( XmlSchema xmlSchemaAnnotation : xmlSchemasAnnotation.value() ) { final String xmlSchemaNamespace = xmlSchemaAnnotation.namespace().trim(); final String xmlSchemaLocation = xmlSchemaAnnotation.location().trim(); if( xmlSchemaNamespace.length() != 0 && xmlSchemaLocation.length() != 0 ) { schemas.put( xmlSchemaNamespace, xmlSchemaLocation ); } } } final XmlSchema xmlSchemaAnnotation = modelElementType.getAnnotation( XmlSchema.class ); if( xmlSchemaAnnotation != null ) { final String xmlSchemaNamespace = xmlSchemaAnnotation.namespace().trim(); final String xmlSchemaLocation = xmlSchemaAnnotation.location().trim(); if( xmlSchemaNamespace.length() != 0 && xmlSchemaLocation.length() != 0 ) { schemas.put( xmlSchemaNamespace, xmlSchemaLocation ); } } this.rootElementController = new StandardRootElementController( namespace, prefix, localName, schemas ); } } } if( this.rootElementController == null ) { this.rootElementController = new StandardRootElementController( createDefaultElementName( modelElementType ) ); } this.rootElementController.init( this ); store().registerRootModelElement( modelElement ); } public final Document getDomDocument() { return this.document; } @Override public XmlElement getXmlElement( final boolean createIfNecessary ) { org.w3c.dom.Element root = this.document.getDocumentElement(); if( this.document.getChildNodes().getLength() == 0 ) { if( createIfNecessary ) { fixMalformedDescriptor(); root = this.document.getDocumentElement(); } } else { final boolean isRootValid = ( root == null ? false : this.rootElementController.checkRootElement() ); if( isRootValid == false ) { root = null; if( createIfNecessary ) { if( validateCorruptedResourceRecovery() ) { fixMalformedDescriptor(); root = this.document.getDocumentElement(); } else { throw new CorruptedResourceException(); } } } } if( root == null ) { this.rootXmlElement = null; } else if( this.rootXmlElement == null || root != this.rootXmlElement.getDomNode() ) { this.rootXmlElement = new XmlElement( store(), root ); } return this.rootXmlElement; } @Override public void save() throws ResourceStoreException { if( this.document.getChildNodes().getLength() == 0 ) { fixMalformedDescriptor(); } this.store.save(); } @Override public <A> A adapt( final Class<A> adapterType ) { A adapter = super.adapt( adapterType ); if( adapter == null ) { adapter = this.store.adapt( adapterType ); } return adapter; } @Override public boolean isOutOfDate() { return this.store.isOutOfDate(); } @Override public void dispose() { super.dispose(); this.store.dispose(); } @Override protected LocalizationService initLocalizationService( final Locale locale ) { return this.store.getLocalizationService( locale ); } private final void fixMalformedDescriptor() { // Remove all of the existing top-level nodes. Note that we have to copy the // items from the node list before removing any as removal seems to alter the // node list. final NodeList topLevelNodes = this.document.getChildNodes(); final Node[] nodes = new Node[ topLevelNodes.getLength() ]; for( int i = 0, n = nodes.length; i < n; i++ ) { nodes[ i ] = topLevelNodes.item( i ); } for( Node node : nodes ) { this.document.removeChild( node ); } // Add a new XML declaration and the root element. if( store().isXmlDeclarationNeeded() ) { final ProcessingInstruction xmlDeclarationNode = this.document.createProcessingInstruction( PI_XML_TARGET, PI_XML_DATA ); this.document.insertBefore( xmlDeclarationNode, null ); final Text newLineTextNode = this.document.createTextNode( "\n" ); this.document.insertBefore( newLineTextNode, null ); } this.rootElementController.createRootElement(); } }