blob: b11d8010d74b8c624987a2a866308e149f3bb4a4 [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.maven.customizer;
import java.awt.Dialog;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
import org.jdom.DefaultJDOMFactory;
import org.jdom.Document;
import org.jdom.JDOMException;
import org.jdom.JDOMFactory;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectUtils;
import org.netbeans.api.project.ui.ProjectProblems;
import org.netbeans.modules.maven.MavenProjectPropsImpl;
import org.netbeans.modules.maven.NbMavenProjectImpl;
import org.netbeans.modules.maven.api.NbMavenProject;
import org.netbeans.modules.maven.api.customizer.ModelHandle;
import org.netbeans.modules.maven.api.customizer.ModelHandle2;
import org.netbeans.modules.maven.api.problem.ProblemReport;
import org.netbeans.modules.maven.configurations.M2ConfigProvider;
import org.netbeans.modules.maven.configurations.M2Configuration;
import org.netbeans.modules.maven.execute.model.ActionToGoalMapping;
import org.netbeans.modules.maven.execute.model.io.jdom.NetbeansBuildActionJDOMWriter;
import org.netbeans.modules.maven.execute.model.io.xpp3.NetbeansBuildActionXpp3Reader;
import org.netbeans.modules.maven.model.Utilities;
import org.netbeans.modules.maven.model.pom.POMModel;
import org.netbeans.modules.maven.model.pom.POMModelFactory;
import org.netbeans.modules.maven.problems.ProblemReporterImpl;
import org.netbeans.modules.xml.xam.Model.State;
import org.netbeans.modules.xml.xam.ModelSource;
import org.netbeans.spi.project.AuxiliaryConfiguration;
import org.netbeans.spi.project.ProjectServiceProvider;
import org.netbeans.spi.project.ui.CustomizerProvider;
import org.netbeans.spi.project.ui.support.ProjectCustomizer;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.filesystems.FileLock;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileSystem;
import org.openide.filesystems.FileUtil;
import org.openide.util.Exceptions;
import org.openide.util.HelpCtx;
import org.openide.util.Lookup;
import org.openide.util.lookup.Lookups;
import static org.netbeans.modules.maven.customizer.Bundle.*;
import org.netbeans.spi.project.ui.CustomizerProvider2;
import org.openide.util.Mutex;
import org.openide.util.NbBundle.Messages;
/**
* maven implementation of CustomizerProvider, handles the general workflow,
*for panel creation delegates to M2CustomizerPanelProvider instances.
* @author Milos Kleint
*/
@ProjectServiceProvider(
service = {
CustomizerProvider.class,
CustomizerProvider2.class,
CustomizerProviderImpl.class
},
projectType = "org-netbeans-modules-maven"
)
public class CustomizerProviderImpl implements CustomizerProvider2 {
public static final HelpCtx HELP_CTX = new HelpCtx("maven_settings");
private final Project project;
private ModelHandle handle;
private ModelHandle2 handle2;
private static final String BROKEN_NBACTIONS = "BROKENNBACTIONS"; //NOI18N
public CustomizerProviderImpl(Project project) {
this.project = project;
}
@Override
public void showCustomizer() {
showCustomizer( null );
}
public void showCustomizer( String preselectedCategory ) {
showCustomizer( preselectedCategory, null );
}
@Messages({
"# {0} - project display name",
"TIT_Project_Properties=Project Properties - {0}",
"ERR_MissingPOM=Project's pom.xml file contains invalid xml content. Please fix the file before proceeding.",
"TXT_Unloadable=Project is unloadable, you have to fix the problems before accessing the project properties dialog. Show Problem Resolution dialog?",
"TIT_Unloadable=Project unlodable"
})
@Override
public void showCustomizer( final String preselectedCategory, String preselectedSubCategory ) {
if (project.getLookup().lookup(NbMavenProject.class).isUnloadable()) {
NotifyDescriptor.Confirmation nd = new NotifyDescriptor.Confirmation(TXT_Unloadable(), TIT_Unloadable());
nd.setOptionType(NotifyDescriptor.YES_NO_OPTION);
if (DialogDisplayer.getDefault().notify(nd) == NotifyDescriptor.YES_OPTION) {
ProjectProblems.showCustomizer(project);
}
return;
}
try {
POMModel mdl = init();
//#171958 start
if (!mdl.getState().equals(State.VALID)) {
NotifyDescriptor nd = new NotifyDescriptor.Message(ERR_MissingPOM(), NotifyDescriptor.ERROR_MESSAGE);
DialogDisplayer.getDefault().notify(nd);
return;
}
//#171958 end
// handle.getPOMModel().startTransaction();
// project.getLookup().lookup(MavenProjectPropsImpl.class).startTransaction();
Mutex.EVENT.readAccess(new Runnable() {
@Override
public void run() {
assert EventQueue.isDispatchThread();
OptionListener listener = new OptionListener();
MavenProjectPropertiesUiSupport uiSupport = new MavenProjectPropertiesUiSupport(handle2, project);
Lookup context = Lookups.fixed(new Object[] { project, handle, handle2, uiSupport});
Dialog dialog = ProjectCustomizer.createCustomizerDialog("Projects/org-netbeans-modules-maven/Customizer", //NOI18N
context,
preselectedCategory,
new ActionListener() {
@Override
public void actionPerformed(ActionEvent ae) {
//noop
}
}, listener, HELP_CTX);
dialog.setTitle( TIT_Project_Properties(ProjectUtils.getInformation(project).getDisplayName()));
dialog.setModal(true);
dialog.setVisible(true);
}
});
} catch (FileNotFoundException ex) {
if ("No pom file exists.".equals(ex.getMessage())) { //NOI18N
//#157020
return;
}
Logger.getLogger(CustomizerProviderImpl.class.getName()).log(Level.SEVERE, "Cannot show project customizer", ex);
} catch (IOException ex) {
Logger.getLogger(CustomizerProviderImpl.class.getName()).log(Level.SEVERE, "Cannot show project customizer", ex);
} catch (XmlPullParserException ex) {
Logger.getLogger(CustomizerProviderImpl.class.getName()).log(Level.SEVERE, "Cannot show project customizer", ex);
}
}
private POMModel init() throws XmlPullParserException, IOException {
FileObject pom = FileUtil.toFileObject(project.getLookup().lookup(NbMavenProjectImpl.class).getPOMFile());
if (pom == null || !pom.isValid()) {
throw new FileNotFoundException("No pom file exists."); //NOI18N
}
ModelSource source = Utilities.createModelSource(pom);
POMModel model = POMModelFactory.getDefault().createFreshModel(source);
Map<String, ActionToGoalMapping> mapps = new HashMap<String, ActionToGoalMapping>();
NetbeansBuildActionXpp3Reader reader = new NetbeansBuildActionXpp3Reader();
List<ModelHandle.Configuration> configs = new ArrayList<ModelHandle.Configuration>();
ModelHandle.Configuration active = null;
M2ConfigProvider provider = project.getLookup().lookup(M2ConfigProvider.class);
M2Configuration act = provider.getActiveConfiguration();
M2Configuration defconfig = provider.getDefaultConfig();
mapps.put(defconfig.getId(), reader.read(new StringReader(defconfig.getRawMappingsAsString())));
ModelHandle.Configuration c = ModelHandle.createDefaultConfiguration();
configs.add(c);
if (act.equals(defconfig)) {
active = c;
}
for (M2Configuration config : provider.getSharedConfigurations()) {
mapps.put(config.getId(), reader.read(new StringReader(config.getRawMappingsAsString())));
c = ModelHandle.createCustomConfiguration(config.getId());
c.setActivatedProfiles(config.getActivatedProfiles());
c.setProperties(config.getProperties());
c.setShared(true);
configs.add(c);
if (act.equals(config)) {
active = c;
}
}
for (M2Configuration config : provider.getNonSharedConfigurations()) {
mapps.put(config.getId(), reader.read(new StringReader(config.getRawMappingsAsString())));
c = ModelHandle.createCustomConfiguration(config.getId());
c.setActivatedProfiles(config.getActivatedProfiles());
c.setProperties(config.getProperties());
c.setShared(false);
configs.add(c);
if (act.equals(config)) {
active = c;
}
}
for (M2Configuration config : provider.getProvidedConfigurations()) {
mapps.put(config.getId(), reader.read(new StringReader(config.getRawMappingsAsString())));
c = ModelHandle.createDefaultConfiguration();
CustomizerProviderImpl.ACCESSOR.setConfigurationId(c, config.getId());
String dn = config.getDisplayName();
if (!config.getId().equals(dn)) {
c.setDisplayName(dn);
}
configs.add(c);
if (act.equals(config)) {
active = c;
}
}
for (M2Configuration config : provider.getProfileConfigurations()) {
mapps.put(config.getId(), reader.read(new StringReader(config.getRawMappingsAsString())));
c = ModelHandle.createProfileConfiguration(config.getId());
configs.add(c);
if (act.equals(config)) {
active = c;
}
}
if (active == null) { //#152706
active = configs.get(0); //default if current not found..
}
handle = ACCESSOR.createHandle(model,
project.getLookup().lookup(NbMavenProject.class).getMavenProject(), mapps, configs, active,
project.getLookup().lookup(MavenProjectPropsImpl.class));
handle2 = ACCESSOR2.createHandle(model,
project.getLookup().lookup(NbMavenProject.class).getMavenProject(), mapps, new ArrayList<ModelHandle2.Configuration>(configs), active,
project.getLookup().lookup(MavenProjectPropsImpl.class));
return model;
}
public static ModelAccessor ACCESSOR = null;
public static ModelAccessor2 ACCESSOR2 = null;
static {
// invokes static initializer of ModelHandle.class
// that will assign value to the ACCESSOR field above
Class c = ModelHandle.class;
try {
Class.forName(c.getName(), true, c.getClassLoader());
} catch (Exception ex) {
ex.printStackTrace();
}
c = ModelHandle2.class;
try {
Class.forName(c.getName(), true, c.getClassLoader());
} catch (Exception ex) {
ex.printStackTrace();
}
}
public abstract static class ModelAccessor {
public abstract void setConfigurationId(ModelHandle.Configuration cfg, String id);
public abstract ModelHandle createHandle(POMModel model, MavenProject proj, Map<String, ActionToGoalMapping> mapp,
List<ModelHandle.Configuration> configs, ModelHandle.Configuration active, MavenProjectPropsImpl auxProps);
}
public abstract static class ModelAccessor2 {
public abstract ModelHandle2 createHandle(POMModel model, MavenProject proj, Map<String, ActionToGoalMapping> mapp,
List<ModelHandle2.Configuration> configs, ModelHandle2.Configuration active, MavenProjectPropsImpl auxProps);
public abstract TreeMap<String, String> getModifiedAuxProps(ModelHandle2 handle, boolean shared);
public abstract boolean isConfigurationModified(ModelHandle2 handle);
public abstract boolean isModified(ModelHandle2 handle, ActionToGoalMapping mapp);
}
/** Listens to the actions on the Customizer's option buttons
ONLY STORE listener now.
*/
private class OptionListener implements ActionListener {
OptionListener() {
}
// Listening to OK button ----------------------------------------------
@Override
public void actionPerformed( ActionEvent e ) {
final FileObject pom = FileUtil.toFileObject(project.getLookup().lookup(NbMavenProjectImpl.class).getPOMFile());
if (pom == null || !pom.isValid()) {
return; //TODO
}
try {
writeAll(handle2, project.getLookup().lookup(NbMavenProjectImpl.class));
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
//TODO error reporting on wrong model save
}
}
}
static interface SubCategoryProvider {
public void showSubCategory(String name);
}
static void writeAll(ModelHandle2 handle, NbMavenProjectImpl project) throws IOException {
//save configs before pom, to save reloads in case both pom and configs were changed.
boolean performConfigsInvokedReload = false;
M2ConfigProvider prv = project.getLookup().lookup(M2ConfigProvider.class);
if (ACCESSOR2.isConfigurationModified(handle)) {
List<M2Configuration> shared = new ArrayList<M2Configuration>();
List<M2Configuration> nonshared = new ArrayList<M2Configuration>();
for (ModelHandle2.Configuration mdlConf : handle.getConfigurations()) {
if (!mdlConf.isDefault() && !mdlConf.isProfileBased()) {
M2Configuration c = new M2Configuration(mdlConf.getId(), project.getProjectDirectory());
c.setActivatedProfiles(mdlConf.getActivatedProfiles());
c.setProperties(mdlConf.getProperties());
if (mdlConf.isShared()) {
shared.add(c);
} else {
nonshared.add(c);
}
}
}
prv.setConfigurations(shared, nonshared, true);
performConfigsInvokedReload = true;
}
final FileObject pom = FileUtil.toFileObject(project.getLookup().lookup(NbMavenProjectImpl.class).getPOMFile());
Utilities.performPOMModelOperations(pom, handle.getPOMOperations());
AuxiliaryConfiguration aux = project.getLookup().lookup(AuxiliaryConfiguration.class);
if (!ACCESSOR2.getModifiedAuxProps(handle, true).isEmpty()) {
MavenProjectPropsImpl.writeAuxiliaryData(aux, ACCESSOR2.getModifiedAuxProps(handle, true), true);
}
if (!ACCESSOR2.getModifiedAuxProps(handle, false).isEmpty()) {
MavenProjectPropsImpl.writeAuxiliaryData(aux, ACCESSOR2.getModifiedAuxProps(handle, false), false);
}
if (ACCESSOR2.isModified(handle, handle.getActionMappings())) {
writeNbActionsModel(project, handle.getActionMappings(), M2Configuration.getFileNameExt(M2Configuration.DEFAULT));
}
//TODO we need to set the configurations for the case of non profile configs
String id = handle.getActiveConfiguration() != null ? handle.getActiveConfiguration().getId() : M2Configuration.DEFAULT;
for (M2Configuration m2 : prv.getConfigurations()) {
if (id.equals(m2.getId())) {
prv.setActiveConfiguration(m2);
}
}
//save action mappings for configurations..
for (ModelHandle2.Configuration c : handle.getConfigurations()) {
if (ACCESSOR2.isModified(handle,handle.getActionMappings(c))) {
writeNbActionsModel(project, handle.getActionMappings(c), M2Configuration.getFileNameExt(c.getId()));
}
}
if (performConfigsInvokedReload && handle.getPOMOperations().isEmpty()) { //#only do the reload if no change to po file was done. can be actually figured now with operations
//#174637
NbMavenProject.fireMavenProjectReload(project);
}
}
public static void writeNbActionsModel(final FileObject pomDir, final ActionToGoalMapping mapping, final String path) throws IOException {
writeNbActionsModel(null, pomDir, mapping, path);
}
public static void writeNbActionsModel(final Project project, final ActionToGoalMapping mapping, final String path) throws IOException {
writeNbActionsModel(project, project.getProjectDirectory(), mapping, path);
}
@Messages({
"TXT_Problem_Broken_Actions=Broken nbactions.xml file.",
"# {0} - exception text",
"DESC_Problem_Broken_Actions=Cannot parse the $project_basedir/nbactions.xml file. The information contained in the file will be ignored until fixed. This affects several features in the IDE that will not work properly as a result.\n\n Parsing exception:\n{0}"})
private static void writeNbActionsModel(final Project project, final FileObject pomDir, final ActionToGoalMapping mapping, final String path) throws IOException {
pomDir.getFileSystem().runAtomicAction(new FileSystem.AtomicAction() {
@Override
public void run() throws IOException {
JDOMFactory factory = new DefaultJDOMFactory();
InputStream inStr = null;
FileLock lock = null;
OutputStreamWriter outStr = null;
try {
Document doc;
if (mapping.getActions().isEmpty()) { //#224450 don't write empty nbactions.xml files
FileObject fo = pomDir.getFileObject(path);
if (fo != null) {
fo.delete();
}
return;
}
FileObject fo = pomDir.getFileObject(path);
if (fo == null) {
fo = pomDir.createData(path);
doc = factory.document(factory.element("actions")); //NOI18N
} else {
//TODO..
inStr = fo.getInputStream();
SAXBuilder builder = new SAXBuilder();
doc = builder.build(inStr);
inStr.close();
inStr = null;
}
lock = fo.lock();
NetbeansBuildActionJDOMWriter writer = new NetbeansBuildActionJDOMWriter();
String encoding = mapping.getModelEncoding() != null ? mapping.getModelEncoding() : "UTF-8"; //NOI18N
outStr = new OutputStreamWriter(fo.getOutputStream(lock), encoding);
Format form = Format.getRawFormat().setEncoding(encoding);
form = form.setLineSeparator(System.getProperty("line.separator")); //NOI18N
writer.write(mapping, doc, outStr, form);
} catch (JDOMException exc){
//throw (IOException) new IOException("Cannot parse the nbactions.xml by JDOM.").initCause(exc); //NOI18N
//TODO this would need it's own problem provider, but how to access it in project lookup if all are merged into one?
NbMavenProjectImpl prj = project != null ? project.getLookup().lookup(NbMavenProjectImpl.class) : null;
ProblemReporterImpl impl = prj != null ? prj.getProblemReporter() : null;
if (impl != null && !impl.hasReportWithId(BROKEN_NBACTIONS)) {
ProblemReport rep = new ProblemReport(ProblemReport.SEVERITY_MEDIUM,
TXT_Problem_Broken_Actions(),
DESC_Problem_Broken_Actions(exc.getMessage()),
ProblemReporterImpl.createOpenFileAction(pomDir.getFileObject(path)));
rep.setId(BROKEN_NBACTIONS);
impl.addReport(rep);
}
Logger.getLogger(CustomizerProviderImpl.class.getName()).log(Level.INFO, exc.getMessage(), exc);
} finally {
IOUtil.close(inStr);
IOUtil.close(outStr);
if (lock != null) {
lock.releaseLock();
}
}
}
});
}
}