blob: c6c9fccc79bd41dc7a3307f9541465657157ffaa [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.
*/
/*
* Contributor(s): Craig MacKay
*/
package org.netbeans.modules.spring.webmvc;
import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.project.classpath.ProjectClassPathModifier;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.SourceGroup;
import org.netbeans.api.project.libraries.Library;
import org.netbeans.modules.j2ee.common.dd.DDHelper;
import org.netbeans.modules.j2ee.core.api.support.SourceGroups;
import org.netbeans.modules.j2ee.dd.api.common.CommonDDBean;
import org.netbeans.modules.j2ee.dd.api.common.CreateCapability;
import org.netbeans.modules.j2ee.dd.api.common.InitParam;
import org.netbeans.modules.j2ee.dd.api.web.DDProvider;
import org.netbeans.modules.j2ee.dd.api.web.Listener;
import org.netbeans.modules.j2ee.dd.api.web.Servlet;
import org.netbeans.modules.j2ee.dd.api.web.ServletMapping;
import org.netbeans.modules.j2ee.dd.api.web.ServletMapping25;
import org.netbeans.modules.j2ee.dd.api.web.WebApp;
import org.netbeans.modules.j2ee.dd.api.web.WelcomeFileList;
import org.netbeans.modules.spring.api.SpringUtilities;
import org.netbeans.modules.spring.api.beans.ConfigFileGroup;
import org.netbeans.modules.spring.api.beans.ConfigFileManager;
import org.netbeans.modules.spring.api.beans.SpringScope;
import org.netbeans.modules.spring.webmvc.utils.SpringWebFrameworkUtils;
import org.netbeans.modules.web.api.webmodule.ExtenderController;
import org.netbeans.modules.web.api.webmodule.WebModule;
import org.netbeans.modules.web.spi.webmodule.WebModuleExtender;
import org.openide.DialogDisplayer;
import org.openide.ErrorManager;
import org.openide.NotifyDescriptor.Message;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileSystem;
import org.openide.filesystems.FileUtil;
import org.openide.loaders.DataFolder;
import org.openide.loaders.DataObject;
import org.openide.util.ChangeSupport;
import org.openide.util.Exceptions;
import org.openide.util.HelpCtx;
import org.openide.util.Mutex.ExceptionAction;
import org.openide.util.MutexException;
import org.openide.util.NbBundle;
/**
* The WebModuleExtender implementation for Spring Web MVC.
*
* @author Craig MacKay et al.
*/
public class SpringWebModuleExtender extends WebModuleExtender implements ChangeListener {
private static final Logger LOGGER = Logger.getLogger(SpringWebModuleExtender.class.getName());
private final ChangeSupport changeSupport = new ChangeSupport(this);
private final SpringWebFrameworkProvider framework;
private final ExtenderController controller;
private final boolean customizer;
private final WebModule webModule;
private SpringConfigPanelVisual component;
private String dispatcherName = "dispatcher"; // NOI18N
private String dispatcherMapping = "*.htm"; // NOI18N
private boolean includeJstl = true;
/**
* Creates a new instance of SpringWebModuleExtender
* @param framework
* @param controller an instance of org.netbeans.modules.web.api.webmodule.ExtenderController
* @param webModule the web module to extend, or {@code null} for new projects.
* @param customizer
*/
public SpringWebModuleExtender(SpringWebFrameworkProvider framework, ExtenderController controller, WebModule webModule, boolean customizer) {
this.framework = framework;
this.controller = controller;
this.webModule = webModule;
this.customizer = customizer;
}
public ExtenderController getController() {
return controller;
}
public String getDispatcherName() {
return dispatcherName;
}
public String getDispatcherMapping() {
return dispatcherMapping;
}
public boolean getIncludeJstl() {
return includeJstl;
}
public synchronized SpringConfigPanelVisual getComponent() {
if (component == null) {
component = new SpringConfigPanelVisual(this);
component.setEnabled(!customizer);
}
return component;
}
public boolean isValid() {
if (dispatcherName == null || dispatcherName.trim().length() == 0){
controller.setErrorMessage(NbBundle.getMessage(SpringConfigPanelVisual.class, "MSG_DispatcherNameIsEmpty")); // NOI18N
return false;
}
if (!SpringWebFrameworkUtils.isDispatcherServletConfigFilenameValid(dispatcherName)){
controller.setErrorMessage(NbBundle.getMessage(SpringConfigPanelVisual.class, "MSG_DispatcherServletConfigFilenameIsNotValid"));
return false;
}
if (dispatcherMapping == null || dispatcherMapping.trim().length() == 0) {
controller.setErrorMessage(NbBundle.getMessage(SpringConfigPanelVisual.class, "MSG_DispatcherMappingPatternIsEmpty")); // NOI18N
return false;
}
if (!SpringWebFrameworkUtils.isDispatcherMappingPatternValid(dispatcherMapping)){
controller.setErrorMessage(NbBundle.getMessage(SpringConfigPanelVisual.class, "MSG_DispatcherMappingPatternIsNotValid")); // NOI18N
return false;
}
if (webModule != null && !isWebXmlValid(webModule)) {
controller.setErrorMessage(NbBundle.getMessage(SpringConfigPanelVisual.class, "MSG_WebXmlIsNotValid")); // NOI18N
return false;
}
if (getComponent().getSpringLibrary() == null) {
controller.setErrorMessage(NbBundle.getMessage(SpringConfigPanelVisual.class, "MSG_NoValidSpringLibraryFound")); // NOI18N
return false;
}
controller.setErrorMessage(null);
return true;
}
public HelpCtx getHelp() {
return new HelpCtx(SpringWebModuleExtender.class);
}
public final void addChangeListener(ChangeListener l) {
changeSupport.addChangeListener(l);
}
public final void removeChangeListener(ChangeListener l) {
changeSupport.removeChangeListener(l);
}
public void stateChanged(ChangeEvent e) {
dispatcherName = getComponent().getDispatcherName();
dispatcherMapping = getComponent().getDispatcherMapping();
includeJstl = getComponent().getIncludeJstl();
changeSupport.fireChange();
}
@Override
public void update() {
// not used yet
}
@Override
public Set<FileObject> extend(WebModule webModule) {
CreateSpringConfig createSpringConfig = new CreateSpringConfig(webModule);
FileObject webInf = webModule.getWebInf();
if (webInf == null) {
try {
FileObject documentBase = webModule.getDocumentBase();
if (documentBase == null) {
LOGGER.log(Level.INFO, "WebModule does not have valid documentBase");
return Collections.<FileObject>emptySet();
}
webInf = FileUtil.createFolder(documentBase, "WEB-INF"); //NOI18N
} catch (IOException ex) {
LOGGER.log(Level.WARNING, "Exception during creating WEB-INF directory", ex);
}
}
if (webInf != null) {
try {
FileSystem fs = webInf.getFileSystem();
fs.runAtomicAction(createSpringConfig);
} catch (IOException e) {
Logger.getLogger("global").log(Level.INFO, null, e);
return Collections.<FileObject>emptySet();
}
}
return createSpringConfig.getFilesToOpen();
}
private boolean isWebXmlValid(WebModule webModule) {
FileObject webXml = webModule.getDeploymentDescriptor();
if (webXml == null) {
return true;
}
WebApp webApp = null;
try {
webApp = DDProvider.getDefault().getDDRoot(webXml);
} catch (IOException ex) {
LOGGER.log(Level.INFO, "Can't read web.xml file: " + webXml.getPath(), ex);
}
return webApp != null && webApp.getStatus() == WebApp.STATE_VALID;
}
private class CreateSpringConfig implements FileSystem.AtomicAction {
public static final String CONTEXT_LOADER = "org.springframework.web.context.ContextLoaderListener"; // NOI18N
public static final String DISPATCHER_SERVLET = "org.springframework.web.servlet.DispatcherServlet"; // NOI18N
public static final String ENCODING = "UTF-8"; // NOI18N
private final Set<FileObject> filesToOpen = new LinkedHashSet<FileObject>();
private final WebModule webModule;
public CreateSpringConfig(WebModule webModule) {
this.webModule = webModule;
}
@NbBundle.Messages({
"CreateSpringConfig.msg.invalid.dd=Deployment descriptor cointains errors, Spring framework has to be manually configured there!"
})
public void run() throws IOException {
// MODIFY WEB.XML
FileObject dd = webModule.getDeploymentDescriptor();
//we need deployment descriptor, create if null
if(dd==null)
{
dd = DDHelper.createWebXml(webModule.getJ2eeProfile(), webModule.getWebInf());
}
if (dd != null) {
WebApp ddRoot = DDProvider.getDefault().getDDRoot(dd);
if (ddRoot.getError() != null) {
Message message = new Message(Bundle.CreateSpringConfig_msg_invalid_dd(), Message.ERROR_MESSAGE);
DialogDisplayer.getDefault().notifyLater(message);
} else {
addContextParam(ddRoot, "contextConfigLocation", "/WEB-INF/applicationContext.xml"); // NOI18N
addListener(ddRoot, CONTEXT_LOADER);
addServlet(ddRoot, getComponent().getDispatcherName(), DISPATCHER_SERVLET, getComponent().getDispatcherMapping(), "2"); // NOI18N
WelcomeFileList welcomeFiles = ddRoot.getSingleWelcomeFileList();
if (welcomeFiles == null) {
try {
welcomeFiles = (WelcomeFileList) ddRoot.createBean("WelcomeFileList"); // NOI18N
ddRoot.setWelcomeFileList(welcomeFiles);
} catch (ClassNotFoundException ex) {
Exceptions.printStackTrace(ex);
}
}
if (welcomeFiles.sizeWelcomeFile() == 0) {
welcomeFiles.addWelcomeFile("redirect.jsp"); // NOI18N
}
ddRoot.write(dd);
}
}
// ADD JSTL LIBRARY IF ENABLED AND SPRING LIBRARY
List<Library> libraries = new ArrayList<Library>(3);
Library springLibrary = component.getSpringLibrary();
String version = component.getSpringLibraryVersion();
Library webMVCLibrary = null;
if (springLibrary != null) {
libraries.add(springLibrary);
if (SpringUtilities.isSpringWebMVCLibrary(springLibrary)) {
webMVCLibrary = springLibrary;
}
} else {
LOGGER.log(Level.WARNING, null, new Error("No Spring Framework library found."));
}
if (webMVCLibrary == null) {
webMVCLibrary = SpringUtilities.findSpringWebMVCLibrary(version);
if (webMVCLibrary !=null) {
libraries.add(webMVCLibrary);
} else {
LOGGER.log(Level.WARNING, null, new Error("No Spring Web MVC library with version "+version+" found."));
}
}
// Library webMVCLibrary = SpringUtilities.findSpringWebMVCLibrary();
// Library springLibrary = null;
// if (webMVCLibrary != null) {
// libraries.add(webMVCLibrary);
// if (SpringUtilities.isSpringLibrary(webMVCLibrary)) {
// // In case this is an user library with a monolithic Spring.
// springLibrary = webMVCLibrary;
// }
// } else {
// LOGGER.log(Level.WARNING, null, new Error("No Spring Web MVC library found."));
// }
// if (springLibrary == null) {
// springLibrary = SpringUtilities.findSpringLibrary();
// if (springLibrary != null){
// libraries.add(springLibrary);
// } else {
// LOGGER.log(Level.WARNING, null, new Error("No Spring Framework library found."));
// }
// }
if (includeJstl) {
Library jstlLibrary = SpringUtilities.findJSTLibrary();
if (jstlLibrary != null) {
libraries.add(jstlLibrary);
} else {
LOGGER.log(Level.WARNING, null, new Error("No JSTL library found."));
}
}
if (!libraries.isEmpty()) {
addLibrariesToWebModule(libraries, webModule);
}
// CREATE WEB-INF/JSP FOLDER
FileObject webInf = webModule.getWebInf();
FileObject jsp = FileUtil.createFolder(webInf, "jsp");
// COPY TEMPLATE SPRING RESOURCES (JSP, XML, PROPERTIES)
DataFolder webInfDO = DataFolder.findFolder(webInf);
final List<File> newConfigFiles = new ArrayList<>(2);
HashMap<String, Object> params = new HashMap<>();
String appContextTemplateName = "applicationContext-4.xml"; //NOI18N
String dispServletTemplateName = "dispatcher-servlet-4.xml"; //NOI18N
if (version.startsWith("3.")) { //NOI18N
appContextTemplateName = "applicationContext-3.xml"; //NOI18N
dispServletTemplateName = "dispatcher-servlet-3.xml"; //NOI18N
}
FileObject configFile = createFromTemplate(appContextTemplateName, webInfDO, "applicationContext",params); // NOI18N
addFileToOpen(configFile);
newConfigFiles.add(FileUtil.toFile(configFile));
String fullIndexUrl = SpringWebFrameworkUtils.instantiateDispatcherMapping(dispatcherMapping, "index"); // NOI18N
String simpleIndexUrl = SpringWebFrameworkUtils.getSimpleDispatcherURL(fullIndexUrl);
Map<String, ?> indexUrlParams = Collections.singletonMap("index", Collections.singletonMap("url", simpleIndexUrl)); // NOI18N
params.putAll(indexUrlParams);
configFile = createFromTemplate(dispServletTemplateName, webInfDO, getComponent().getDispatcherName() + "-servlet", params); // NOI18N
addFileToOpen(configFile);
newConfigFiles.add(FileUtil.toFile(configFile));
addFileToOpen(createFromTemplate("index.jsp", DataFolder.findFolder(jsp), "index")); // NOI18N
// MODIFY EXISTING REDIRECT.JSP
indexUrlParams = Collections.singletonMap("index", Collections.singletonMap("url", fullIndexUrl)); // NOI18N
FileObject documentBase = webModule.getDocumentBase();
FileObject redirectJsp = documentBase.getFileObject("redirect.jsp"); // NOI18N
if (redirectJsp != null) {
redirectJsp.delete();
}
DataFolder documentBaseDO = DataFolder.findFolder(documentBase);
addFileToOpen(createFromTemplate("redirect.jsp", documentBaseDO, "redirect", indexUrlParams));
SpringScope scope = SpringScope.getSpringScope(configFile);
if (scope != null) {
final ConfigFileManager manager = scope.getConfigFileManager();
try {
manager.mutex().writeAccess(new ExceptionAction<Void>() {
public Void run() throws IOException {
List<File> files = manager.getConfigFiles();
files.addAll(newConfigFiles);
List<ConfigFileGroup> groups = manager.getConfigFileGroups();
String groupName = NbBundle.getMessage(SpringWebModuleExtender.class, "LBL_DefaultGroup");
ConfigFileGroup newGroup = ConfigFileGroup.create(groupName, newConfigFiles);
groups.add(newGroup);
manager.putConfigFilesAndGroups(files, groups);
manager.save();
return null;
}
});
} catch (MutexException e) {
throw (IOException)e.getException();
}
} else {
LOGGER.log(Level.WARNING, "Could not find a SpringScope for file {0}", configFile);
}
}
public void addFileToOpen(FileObject file) {
filesToOpen.add(file);
}
public Set<FileObject> getFilesToOpen() {
return filesToOpen;
}
private FileObject createFromTemplate(String templateName, DataFolder targetDO, String fileName, Map<String, ?> params) throws IOException {
FileObject templateFO = FileUtil.getConfigFile("SpringFramework/Templates/" + templateName);
DataObject templateDO = DataObject.find(templateFO);
return templateDO.createFromTemplate(targetDO, fileName, params).getPrimaryFile();
}
private FileObject createFromTemplate(String templateName, DataFolder targetDO, String fileName) throws IOException {
FileObject templateFO = FileUtil.getConfigFile("SpringFramework/Templates/" + templateName);
DataObject templateDO = DataObject.find(templateFO);
return templateDO.createFromTemplate(targetDO, fileName).getPrimaryFile();
}
protected boolean addLibrariesToWebModule(List<Library> libraries, WebModule webModule) throws IOException, UnsupportedOperationException {
FileObject fileObject = webModule.getDocumentBase();
Project project = FileOwnerQuery.getOwner(fileObject);
if (project == null) {
return false;
}
boolean addLibraryResult = false;
try {
SourceGroup[] groups = SourceGroups.getJavaSourceGroups(project);
if (groups.length == 0) {
return false;
}
addLibraryResult = ProjectClassPathModifier.addLibraries(libraries.toArray(new Library[libraries.size()]), groups[0].getRootFolder(), ClassPath.COMPILE);
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Libraries required for the Spring MVC project not added", e); // NOI18N
} catch (UnsupportedOperationException uoe) {
LOGGER.log(Level.WARNING, "This project does not support adding these types of libraries to the classpath", uoe); // NOI18N
}
return addLibraryResult;
}
protected Listener addListener(WebApp webApp, String classname) throws IOException {
Listener listener = (Listener) createBean(webApp, "Listener"); // NOI18N
listener.setListenerClass(classname);
webApp.addListener(listener);
return listener;
}
protected Servlet addServlet(WebApp webApp, String name, String classname, String pattern, String loadOnStartup) throws IOException {
Servlet servlet = (Servlet) createBean(webApp, "Servlet"); // NOI18N
servlet.setServletName(name);
servlet.setServletClass(classname);
if (loadOnStartup != null) {
servlet.setLoadOnStartup(new BigInteger(loadOnStartup));
}
webApp.addServlet(servlet);
if (pattern != null) {
addServletMapping(webApp, name, pattern);
}
return servlet;
}
protected ServletMapping addServletMapping(WebApp webApp, String name, String pattern) throws IOException {
ServletMapping25 mapping = (ServletMapping25) createBean(webApp, "ServletMapping"); // NOI18N
mapping.setServletName(name);
mapping.addUrlPattern(pattern);
webApp.addServletMapping(mapping);
return mapping;
}
protected InitParam addContextParam(WebApp webApp, String name, String value) throws IOException {
InitParam initParam = (InitParam) createBean(webApp, "InitParam"); // NOI18N
initParam.setParamName(name);
initParam.setParamValue(value);
webApp.addContextParam(initParam);
return initParam;
}
protected CommonDDBean createBean(CreateCapability creator, String beanName) throws IOException {
CommonDDBean bean = null;
try {
bean = creator.createBean(beanName);
} catch (ClassNotFoundException ex) {
ErrorManager.getDefault().notify(ErrorManager.EXCEPTION, ex);
throw new IOException("Error creating bean with name:" + beanName); // NOI18N
}
return bean;
}
}
}