Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* 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 com.intellij.struts2.annotators;

import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.lang.annotation.AnnotationHolder;
import com.intellij.lang.annotation.Annotator;
import com.intellij.lang.annotation.HighlightSeverity;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ui.configuration.ModulesConfigurator;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.jsp.JspFile;
import com.intellij.psi.xml.XmlFile;
import com.intellij.struts2.StrutsBundle;
import com.intellij.struts2.dom.struts.model.StrutsManager;
import com.intellij.struts2.facet.StrutsFacet;
import com.intellij.struts2.facet.WebFacetChecker;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;

/**
* Warns when a {@code struts.xml} file is opened in a module that has a Struts
* facet but no {@link com.intellij.javaee.web.facet.WebFacet}, which prevents
* dispatch-style result paths (JSP etc.) from resolving.
*/
public class StrutsWebFacetCheckingAnnotator implements Annotator {

@Override
public void annotate(@NotNull PsiElement psiElement, @NotNull AnnotationHolder holder) {
if (!(psiElement instanceof XmlFile xmlFile)) {
return;
}

if (psiElement instanceof JspFile) {
return;
}

Module module = ModuleUtilCore.findModuleForPsiElement(psiElement);
if (module == null) {
return;
}

StrutsFacet strutsFacet = StrutsFacet.getInstance(module);
if (strutsFacet == null) {
return;
}

StrutsManager strutsManager = StrutsManager.getInstance(psiElement.getProject());
if (!strutsManager.isStruts2ConfigFile(xmlFile)) {
return;
}

if (!WebFacetChecker.isWebFacetMissing(module)) {
return;
}

holder.newAnnotation(HighlightSeverity.WARNING,
StrutsBundle.message("annotators.webfacet.missing"))
.range(xmlFile)
.fileLevel()
.withFix(new ConfigureWebFacetFix(strutsFacet))
.create();
}

private static final class ConfigureWebFacetFix implements IntentionAction {

private final StrutsFacet myStrutsFacet;

private ConfigureWebFacetFix(@NotNull StrutsFacet strutsFacet) {
myStrutsFacet = strutsFacet;
}

@Override
@NotNull
public String getText() {
return StrutsBundle.message("annotators.webfacet.configure");
}

@Override
@NotNull
public String getFamilyName() {
return StrutsBundle.message("intentions.family.name");
}

@Override
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile psiFile) {
return true;
}

@Override
public void invoke(@NotNull Project project, Editor editor, PsiFile psiFile)
throws IncorrectOperationException {
ModulesConfigurator.showFacetSettingsDialog(myStrutsFacet, null);
}

@Override
public boolean startInWriteAction() {
return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.struts2.StrutsBundle;
import com.intellij.struts2.StrutsConstants;
import com.intellij.struts2.facet.ui.StrutsConfigsSearcher;
import com.intellij.struts2.facet.ui.StrutsFileSet;
Expand Down Expand Up @@ -127,16 +128,7 @@ public Object execute(@NotNull Project project, @NotNull Continuation<? super Un
return data.strutsFacet;
}

// Check for existing facets (project reopened) - no initialization needed
ModuleManager moduleManager = ModuleManager.getInstance(project);
Module[] modules = moduleManager.getModules();
LOG.debug("Checking " + modules.length + " modules for existing Struts facets");
for (Module module : modules) {
StrutsFacet facet = StrutsFacet.getInstance(module);
if (facet != null) {
LOG.debug("Found existing Struts facet in module: " + module.getName() + ", no initialization needed");
}
}
checkWebFacetForAllModules(project);
return null;
}

Expand Down Expand Up @@ -295,4 +287,38 @@ private void showErrorNotification(@NotNull Project project, @NotNull Exception
NotificationType.ERROR)
.notify(project);
}

/**
* Checks all modules with a Struts facet for a missing WebFacet and
* emits one notification per affected module (once per project open).
*/
private void checkWebFacetForAllModules(@NotNull Project project) {
Module[] modules = ModuleManager.getInstance(project).getModules();
for (Module module : modules) {
if (WebFacetChecker.isWebFacetMissing(module)) {
showMissingWebFacetNotification(project, module);
}
}
}

private void showMissingWebFacetNotification(@NotNull Project project, @NotNull Module module) {
StrutsFacet strutsFacet = StrutsFacet.getInstance(module);
if (strutsFacet == null) return;

new Notification("Struts 2",
StrutsBundle.message("annotators.webfacet.notification.title"),
StrutsBundle.message("annotators.webfacet.notification.content", module.getName()),
NotificationType.WARNING)
.addAction(new NotificationAction(
StrutsBundle.message("annotators.webfacet.configure")) {
@Override
public void actionPerformed(@NotNull AnActionEvent e, @NotNull Notification notification) {
notification.expire();
ModulesConfigurator.showFacetSettingsDialog(strutsFacet, null);
}
})
.notify(project);

LOG.info("Missing WebFacet notification shown for module: " + module.getName());
}
}
59 changes: 59 additions & 0 deletions src/main/java/com/intellij/struts2/facet/WebFacetChecker.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* 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 com.intellij.struts2.facet;

import com.intellij.openapi.module.Module;
import com.intellij.javaee.web.WebUtil;
import com.intellij.javaee.web.facet.WebFacet;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;

/**
* Checks whether a module with a Struts facet has the required {@link WebFacet}
* for JSP/dispatch-style result path resolution.
* <p>
* Shared between the file-level annotator and the framework initialization
* notification so the condition is defined in one place.
*/
public final class WebFacetChecker {

private WebFacetChecker() {}

/**
* Returns {@code true} when the given module has a Struts facet but lacks
* a usable {@link WebFacet}, meaning dispatch-style result paths
* (e.g. {@code /index.jsp}) cannot be resolved.
*/
public static boolean isWebFacetMissing(@NotNull Module module) {
StrutsFacet strutsFacet = StrutsFacet.getInstance(module);
if (strutsFacet == null) return false;
return strutsFacet.getWebFacet() == null;
}

/**
* Variant that also checks via {@link WebUtil#getWebFacet(PsiElement)},
* which searches the module and its dependants.
*/
public static boolean isWebFacetMissing(@NotNull PsiElement element) {
Module module = ModuleUtilCore.findModuleForPsiElement(element);
if (module == null) return false;
StrutsFacet strutsFacet = StrutsFacet.getInstance(module);
if (strutsFacet == null) return false;
return WebUtil.getWebFacet(element) == null;
}
}
1 change: 1 addition & 0 deletions src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@
implementationClass="com.intellij.struts2.dom.params.ParamNameConverterImpl"/>

<annotator language="XML" implementationClass="com.intellij.struts2.annotators.StrutsFileSetCheckingAnnotator"/>
<annotator language="XML" implementationClass="com.intellij.struts2.annotators.StrutsWebFacetCheckingAnnotator"/>
<compiler.inspectionValidator implementation="com.intellij.struts2.dom.inspection.Struts2ModelValidator"/>

<projectService serviceInterface="com.intellij.struts2.dom.struts.model.StrutsManager"
Expand Down
5 changes: 5 additions & 0 deletions src/main/resources/messages/Struts2Bundle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ annotators.fileset.edit.facet.settings=Edit Struts 2 facet settings
annotators.fileset.fix.add.to.fileset=Add {0} to file set
annotators.fileset.fix.choose.fileset=Choose File Set

annotators.webfacet.missing=Web facet not configured \u2013 JSP result paths and Diagram navigation cannot resolve without a Web facet
annotators.webfacet.configure=Configure Web facet in module settings
annotators.webfacet.notification.title=Web facet not configured
annotators.webfacet.notification.content=Module ''{0}'' has a Struts 2 facet but no Web facet. JSP result paths and Diagram navigation will not resolve correctly.

dom.extendable.class.converter.type.class=class
dom.extendable.class.converter.type.spring=Spring bean
dom.extendable.class.converter.cannot.resolve=Cannot resolve {0} ''{1}''
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public final class Struts2ProjectDescriptorBuilder extends DefaultLightProjectDe
private boolean addStrutsLibrary;
private boolean addStrutsFacet;
private boolean addWebFacet;
private boolean skipWebFacet;

private final List<String> mavenLibs = new SmartList<>();
private final List<Callback> callbacks = new SmartList<>();
Expand All @@ -71,6 +72,15 @@ public Struts2ProjectDescriptorBuilder withStrutsFacet() {
return this;
}

/**
* Prevents the default WebFacet from being added, so tests can verify
* behaviour when no WebFacet is present.
*/
public Struts2ProjectDescriptorBuilder withoutWebFacet() {
skipWebFacet = true;
return this;
}

public Struts2ProjectDescriptorBuilder withCallback(Callback callback) {
callbacks.add(callback);
return this;
Expand Down Expand Up @@ -106,21 +116,25 @@ public void configureModule(@NotNull Module module, @NotNull ModifiableRootModel
MavenDependencyUtil.addFromMaven(model, lib);
}

final WebFacet webFacet = FacetUtil.addFacet(module, WebFacetType.getInstance());
if (addStrutsFacet) {
FacetManager.getInstance(module).addFacet(StrutsFacetType.getInstance(), "struts2", null);
}
if (!skipWebFacet) {
final WebFacet webFacet = FacetUtil.addFacet(module, WebFacetType.getInstance());
if (addStrutsFacet) {
FacetManager.getInstance(module).addFacet(StrutsFacetType.getInstance(), "struts2", null);
}

if (addWebFacet) {
final String sourceRootUrl = model.getSourceRootUrls()[0];
webFacet.addWebRoot(sourceRootUrl, "/");
if (addWebFacet) {
final String sourceRootUrl = model.getSourceRootUrls()[0];
webFacet.addWebRoot(sourceRootUrl, "/");

final ConfigFileInfoSet descriptors = webFacet.getDescriptorsContainer().getConfiguration();
descriptors.addConfigFile(DeploymentDescriptorsConstants.WEB_XML_META_DATA, sourceRootUrl + "/WEB-INF/web.xml");
final ConfigFileInfoSet descriptors = webFacet.getDescriptorsContainer().getConfiguration();
descriptors.addConfigFile(DeploymentDescriptorsConstants.WEB_XML_META_DATA, sourceRootUrl + "/WEB-INF/web.xml");

for (String url : ModuleRootManager.getInstance(module).getSourceRootUrls()) {
webFacet.addWebSourceRoot(url);
for (String url : ModuleRootManager.getInstance(module).getSourceRootUrls()) {
webFacet.addWebSourceRoot(url);
}
}
} else if (addStrutsFacet) {
FacetManager.getInstance(module).addFacet(StrutsFacetType.getInstance(), "struts2", null);
}

for (Callback callback : callbacks) {
Expand Down Expand Up @@ -157,6 +171,7 @@ public boolean equals(Object o) {
if (addStrutsLibrary != builder.addStrutsLibrary) return false;
if (addStrutsFacet != builder.addStrutsFacet) return false;
if (addWebFacet != builder.addWebFacet) return false;
if (skipWebFacet != builder.skipWebFacet) return false;
if (!callbacks.equals(builder.callbacks)) return false;
if (!mavenLibs.equals(builder.mavenLibs)) return false;

Expand All @@ -168,6 +183,7 @@ public int hashCode() {
int result = (addStrutsLibrary ? 1 : 0);
result = 31 * result + (addStrutsFacet ? 1 : 0);
result = 31 * result + (addWebFacet ? 1 : 0);
result = 31 * result + (skipWebFacet ? 1 : 0);
result = 31 * result + mavenLibs.hashCode();
result = 31 * result + callbacks.hashCode();
return result;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* 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 com.intellij.struts2.annotators;

import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.struts2.BasicLightHighlightingTestCase;
import com.intellij.struts2.Struts2ProjectDescriptorBuilder;
import com.intellij.testFramework.LightProjectDescriptor;
import org.jetbrains.annotations.NotNull;

/**
* Tests {@link StrutsWebFacetCheckingAnnotator}.
*/
public class StrutsWebFacetCheckingAnnotatorTest extends BasicLightHighlightingTestCase {

private static final LightProjectDescriptor NO_WEB_FACET =
new Struts2ProjectDescriptorBuilder()
.withStrutsLibrary()
.withStrutsFacet()
.withoutWebFacet()
.build();

@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
return NO_WEB_FACET;
}

@Override
@NotNull
protected String getTestDataLocation() {
return "strutsXml/highlighting";
}

public void testWebFacetMissingWarning() {
createStrutsFileSet("struts-simple.xml");
IntentionAction intention = myFixture.getAvailableIntention(
"Configure Web facet in module settings", "struts-simple.xml");
assertNotNull("WebFacet warning quick-fix should be available",
intention);
}
}
Loading
Loading