blob: 2593b0d23b17ac682814883e4ae430398e815833 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.j2ee.dd.api.application;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.netbeans.modules.j2ee.dd.api.common.CommonDDBean;
import org.netbeans.modules.j2ee.dd.impl.application.ApplicationProxy;
import org.netbeans.modules.schema2beans.BaseBean;
import org.netbeans.modules.schema2beans.Common;
import org.openide.filesystems.FileChangeAdapter;
import org.openide.filesystems.FileEvent;
import org.openide.filesystems.FileObject;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
/**
* Provides access to Deployment Descriptor root ({@link Application} object)
*
* @author Milan Kuchtiak
*/
public final class DDProvider {
private static final String APP_13_DOCTYPE = "-//Sun Microsystems, Inc.//DTD J2EE Application 1.3//EN"; //NOI18N
private static final DDProvider ddProvider = new DDProvider();
private final Map<FileObject, ApplicationProxy> ddMap;
private static final Logger LOGGER = Logger.getLogger(DDProvider.class.getName());
ResourceBundle bundle = ResourceBundle.getBundle("org/netbeans/modules/j2ee/dd/Bundle");
private DDProvider() {
//ddMap=new java.util.WeakHashMap(5);
ddMap = new HashMap<>(5);
}
/**
* Accessor method for DDProvider singleton
* @return DDProvider object
*/
public static DDProvider getDefault() {
return ddProvider;
}
/**
* Returns the root of deployment descriptor bean graph for given file object.
* The method is useful for clients planning to read only the deployment descriptor
* or to listen to the changes.
* <p>
* There is no guarantee the return value will contain all the changes
* which happened in the underlaying file recently due to caching.
*
* @param fo FileObject representing the application.xml file
* @return Application object - root of the deployment descriptor bean graph
* @throws IOException
*/
public synchronized Application getDDRoot(FileObject fo) throws IOException {
if (fo == null) {
return null;
}
ApplicationProxy applicationProxy = null;
synchronized (ddMap) {
applicationProxy = getFromCache (fo);
if (applicationProxy!=null) {
return applicationProxy;
}
}
fo.addFileChangeListener(new FileChangeAdapter() {
@Override
public void fileChanged(FileEvent evt) {
FileObject fo=evt.getFile();
try {
synchronized (ddMap) {
ApplicationProxy applicationProxy = getFromCache (fo);
String version = null;
if (applicationProxy!=null) {
try {
DDParse parseResult = parseDD(fo);
version = parseResult.getVersion();
setProxyErrorStatus(applicationProxy, parseResult);
Application newValue = createApplication(parseResult);
// replacing original file in proxy EjbJar
if (!version.equals(applicationProxy.getVersion().toString())) {
applicationProxy.setOriginal(newValue);
} else {// the same version
// replacing original file in proxy EjbJar
if (applicationProxy.getOriginal()==null) {
applicationProxy.setOriginal(newValue);
} else {
applicationProxy.getOriginal().merge(newValue,Application.MERGE_UPDATE);
}
}
} catch (SAXException ex) {
if (ex instanceof SAXParseException) {
applicationProxy.setError((SAXParseException)ex);
} else if ( ex.getException() instanceof SAXParseException) {
applicationProxy.setError((SAXParseException)ex.getException());
}
applicationProxy.setStatus(Application.STATE_INVALID_UNPARSABLE);
// cbw if the state of the xml file transitions from
// parsable to unparsable this could be due to a user
// change or cvs change. We would like to still
// receive events when the file is restored to normal
// so lets not set the original to null here but wait
// until the file becomes parsable again to do a merge
//ejbJarProxy.setOriginal(null);
applicationProxy.setProxyVersion(version);
}
}
}
} catch (IOException ex){
LOGGER.log(Level.INFO, "Merging of Application graphs failed", ex); //NOI18N
}
}
});
try {
DDParse parseResult = parseDD(fo);
Application original = createApplication(parseResult);
applicationProxy = new ApplicationProxy(original,parseResult.getVersion());
setProxyErrorStatus(applicationProxy, parseResult);
} catch (SAXException ex) {
// XXX lets throw an exception here
applicationProxy = new ApplicationProxy(org.netbeans.modules.j2ee.dd.impl.application.model_1_4.Application.createGraph(),"2.0");
applicationProxy.setStatus(Application.STATE_INVALID_UNPARSABLE);
if (ex instanceof SAXParseException) {
applicationProxy.setError((SAXParseException)ex);
} else if ( ex.getException() instanceof SAXParseException) {
applicationProxy.setError((SAXParseException)ex.getException());
}
}
synchronized(ddMap){
ApplicationProxy cached = getFromCache(fo);
if (cached != null){
return cached;
}
ddMap.put(fo, /*new WeakReference*/ (applicationProxy));
}
return applicationProxy;
}
/**
* Returns the root of deployment descriptor bean graph for given file object.
* The method is useful for clients planning to modify the deployment descriptor.
* Finally the {@link Application#write(org.openide.filesystems.FileObject)} should be used
* for writing the changes.
* @param fo FileObject representing the application.xml file
* @return Application object - root of the deployment descriptor bean graph
* @throws IOException
*/
public Application getDDRootCopy(FileObject fo) throws IOException {
return (Application)getDDRoot(fo).clone();
}
private ApplicationProxy getFromCache (FileObject fo) {
return ddMap.get(fo);
}
/**
* Returns the root of deployment descriptor bean graph for java.io.File object.
*
* @param is source representing the application.xml file
* @return Application object - root of the deployment descriptor bean graph
* @throws IOException
* @throws SAXException
*/
public Application getDDRoot(InputSource is) throws IOException, SAXException {
DDParse parse = parseDD(is);
Application application = createApplication(parse);
ApplicationProxy proxy = new ApplicationProxy(application, application.getVersion().toString());
setProxyErrorStatus(proxy, parse);
return proxy;
}
// PENDING j2eeserver needs BaseBean - this is a temporary workaround to avoid dependency of web project on DD impl
/**
* Convenient method for getting the BaseBean object from CommonDDBean object.
* @param bean
* @return
*/
public BaseBean getBaseBean(CommonDDBean bean) {
if (bean instanceof BaseBean) {
return (BaseBean)bean;
} else if (bean instanceof ApplicationProxy) {
return (BaseBean) ((ApplicationProxy)bean).getOriginal();
}
return null;
}
private static void setProxyErrorStatus(ApplicationProxy applicationProxy, DDParse parse) {
SAXParseException error = parse.getWarning();
applicationProxy.setError(error);
if (error!=null) {
applicationProxy.setStatus(Application.STATE_INVALID_PARSABLE);
} else {
applicationProxy.setStatus(Application.STATE_VALID);
}
}
private static Application createApplication(DDParse parse) {
Application jar = null;
String version = parse.getVersion();
if (Application.VERSION_1_4.equals(version)) {
return new org.netbeans.modules.j2ee.dd.impl.application.model_1_4.Application(parse.getDocument(), Common.USE_DEFAULT_VALUES);
} else if (Application.VERSION_5.equals(version)) {
return new org.netbeans.modules.j2ee.dd.impl.application.model_5.Application(parse.getDocument(), Common.USE_DEFAULT_VALUES);
} else if (Application.VERSION_6.equals(version)) {
return new org.netbeans.modules.j2ee.dd.impl.application.model_6.Application(parse.getDocument(), Common.USE_DEFAULT_VALUES);
} else if (Application.VERSION_7.equals(version)) {
return new org.netbeans.modules.j2ee.dd.impl.application.model_7.Application(parse.getDocument(), Common.USE_DEFAULT_VALUES);
} else if (Application.VERSION_8.equals(version)) {
return new org.netbeans.modules.j2ee.dd.impl.application.model_8.Application(parse.getDocument(), Common.USE_DEFAULT_VALUES);
} else if (Application.VERSION_9.equals(version)) {
return new org.netbeans.modules.j2ee.dd.impl.application.model_9.Application(parse.getDocument(), Common.USE_DEFAULT_VALUES);
}
return jar;
}
private static class DDResolver implements EntityResolver {
static DDResolver resolver;
static synchronized DDResolver getInstance() {
if (resolver==null) {
resolver=new DDResolver();
}
return resolver;
}
@Override
public InputSource resolveEntity (String publicId, String systemId) {
if ("http://java.sun.com/xml/ns/j2ee/application_1_4.xsd".equals(systemId)) {
return new InputSource("nbres:/org/netbeans/modules/j2ee/dd/impl/resources/application_1_4.xsd"); //NOI18N
} else if ("http://java.sun.com/xml/ns/javaee/application_5.xsd".equals(systemId)) {
return new InputSource("nbres:/org/netbeans/modules/javaee/dd/impl/resources/application_5.xsd"); //NOI18N
} else if ("http://java.sun.com/xml/ns/javaee/application_6.xsd".equals(systemId)) {
return new InputSource("nbres:/org/netbeans/modules/javaee/dd/impl/resources/application_6.xsd"); //NOI18N
} else if ("http://xmlns.jcp.org/xml/ns/javaee/application_7.xsd".equals(systemId)) {
return new InputSource("nbres:/org/netbeans/modules/javaee/dd/impl/resources/application_7.xsd"); //NOI18N
} else if ("http://xmlns.jcp.org/xml/ns/javaee/application_8.xsd".equals(systemId)) {
return new InputSource("nbres:/org/netbeans/modules/javaee/dd/impl/resources/application_8.xsd"); //NOI18N
} else if ("https://jakarta.ee/xml/ns/jakartaee/application_9.xsd".equals(systemId)) {
return new InputSource("nbres:/org/netbeans/modules/javaee/dd/impl/resources/application_9.xsd"); //NOI18N
} else {
// use the default behaviour
return null;
}
}
}
private static class ErrorHandler implements org.xml.sax.ErrorHandler {
private int errorType=-1;
SAXParseException error;
@Override
public void warning(SAXParseException sAXParseException) throws SAXException {
if (errorType<0) {
errorType=0;
error=sAXParseException;
}
//throw sAXParseException;
}
@Override
public void error(SAXParseException sAXParseException) throws SAXException {
if (errorType<1) {
errorType=1;
error=sAXParseException;
}
//throw sAXParseException;
}
@Override
public void fatalError(SAXParseException sAXParseException) throws SAXException {
errorType=2;
throw sAXParseException;
}
public int getErrorType() {
return errorType;
}
public SAXParseException getError() {
return error;
}
}
public SAXParseException parse(FileObject fo)
throws SAXException, IOException {
DDParse parseResult = parseDD(fo);
return parseResult.getWarning();
}
private DDParse parseDD (FileObject fo)
throws SAXException, IOException {
try (InputStream inputStream = fo.getInputStream()) {
return parseDD(inputStream);
}
}
private DDParse parseDD (InputStream is)
throws SAXException, IOException {
return parseDD(new InputSource(is));
}
private DDParse parseDD (InputSource is)
throws SAXException, IOException {
DDProvider.ErrorHandler errorHandler = new DDProvider.ErrorHandler();
DocumentBuilder parser=null;
try {
DocumentBuilderFactory fact = DocumentBuilderFactory.newInstance();
parser = fact.newDocumentBuilder();
} catch (ParserConfigurationException ex) {
throw new SAXException(ex.getMessage());
}
parser.setErrorHandler(errorHandler);
parser.setEntityResolver(DDResolver.getInstance());
Document d = parser.parse(is);
SAXParseException error = errorHandler.getError();
return new DDParse(d, error);
}
/**
* This class represents one parse of the deployment descriptor
*/
private static class DDParse {
private final Document document;
private final SAXParseException saxException;
private String version;
public DDParse(Document d, SAXParseException saxEx) {
document = d;
saxException = saxEx;
extractVersion();
}
/**
* @return document from last parse
*/
public Document getDocument() {
return document;
}
/**
* @return version of deployment descriptor.
*/
private void extractVersion () {
// This is the default version
version = Application.VERSION_7;
// first check the doc type to see if there is one
DocumentType dt = document.getDoctype();
if(dt == null) {
//check application node version attribute
NodeList nl = document.getElementsByTagName("application");//NOI18N
if(nl != null && nl.getLength() > 0) {
Node appNode = nl.item(0);
NamedNodeMap attrs = appNode.getAttributes();
Node vNode = attrs.getNamedItem("version");//NOI18N
if(vNode != null) {
String versionValue = vNode.getNodeValue();
if (Application.VERSION_9.equals(versionValue)) {
version = Application.VERSION_9;
} else if (Application.VERSION_8.equals(versionValue)) {
version = Application.VERSION_8;
} else if (Application.VERSION_7.equals(versionValue)) {
version = Application.VERSION_7;
} else if (Application.VERSION_6.equals(versionValue)) {
version = Application.VERSION_6;
} else if (Application.VERSION_5.equals(versionValue)) {
version = Application.VERSION_5;
} else if (Application.VERSION_1_4.equals(versionValue)) {
version = Application.VERSION_1_4;
} else {
version = Application.VERSION_7; //default
}
}
}
}
}
public String getVersion() {
return version;
}
/**
* @return validation error encountered during the parse
*/
public SAXParseException getWarning() {
return saxException;
}
}
}