commit 2bfdf6b9de92b3617b891dfa3e1071162486f4ee Author: Bo Pan Date: Fri May 8 22:26:44 2026 -0600 initial upload Co-authored-by: Copilot diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f69ae64 --- /dev/null +++ b/.gitignore @@ -0,0 +1,46 @@ +# ---> VisualStudioCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +*.DS_Store \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..7c85317 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,14 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "java", + "name": "Current File", + "request": "launch", + "mainClass": "${file}" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..0a22d03 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,19 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "start app server", + "type": "shell", + "command": "mvn jetty:run -pl *-server -am -Denv=dev", + "group": "test" + }, + { + "label": "start gwt code server", + "type": "shell", + "command": "mvn gwt:codeserver -pl *-client -am", + "group": "test" + } + ] +} \ No newline at end of file diff --git a/gwtdemo-client/pom.xml b/gwtdemo-client/pom.xml new file mode 100644 index 0000000..25e660a --- /dev/null +++ b/gwtdemo-client/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + + space.barrx + gwtdemo + 1.0-SNAPSHOT + + + gwtdemo-client + gwt-app + + + + ${project.groupId} + gwtdemo-shared + ${project.version} + + + ${project.groupId} + gwtdemo-shared + ${project.version} + sources + + + org.gwtproject + gwt-user + + + org.gwtproject + gwt-dev + + + + + + + net.ltgt.gwt.maven + gwt-maven-plugin + + space.barrx.gwtdemo.App + gwtapp + + + + + diff --git a/gwtdemo-client/src/main/java/space/barrx/gwtdemo/App.java b/gwtdemo-client/src/main/java/space/barrx/gwtdemo/App.java new file mode 100644 index 0000000..34ff21a --- /dev/null +++ b/gwtdemo-client/src/main/java/space/barrx/gwtdemo/App.java @@ -0,0 +1,163 @@ +package space.barrx.gwtdemo; + +import com.google.gwt.core.client.EntryPoint; +import com.google.gwt.core.client.GWT; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.event.dom.client.KeyUpEvent; +import com.google.gwt.event.dom.client.KeyUpHandler; +import com.google.gwt.safehtml.shared.SafeHtmlBuilder; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.DialogBox; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.RootPanel; +import com.google.gwt.user.client.ui.TextBox; +import com.google.gwt.user.client.ui.VerticalPanel; + +/** + * Entry point classes define onModuleLoad(). + */ +public class App implements EntryPoint { + /** + * The message displayed to the user when the server cannot be reached or + * returns an error. + */ + private static final String SERVER_ERROR = "An error occurred while " + + "attempting to contact the server. Please check your network " + + "connection and try again."; + + /** + * Create a remote service proxy to talk to the server-side Greeting service. + */ + private final GreetingServiceAsync greetingService = GWT + .create(GreetingService.class); + + /** + * This is the entry point method. + */ + public void onModuleLoad() { + // Create and add the menu presenter + MenuViewImpl menuView = new MenuViewImpl(); + MenuPresenter menuPresenter = new MenuPresenter(menuView); + RootPanel.get("menuContainer").add(menuView); + + final Button sendButton = new Button("Send"); + final TextBox nameField = new TextBox(); + nameField.setText("GWT User"); + final Label errorLabel = new Label(); + + // We can add style names to widgets + sendButton.addStyleName("sendButton"); + + // Add the nameField and sendButton to the RootPanel + // Use RootPanel.get() to get the entire body element + RootPanel.get("nameFieldContainer").add(nameField); + RootPanel.get("sendButtonContainer").add(sendButton); + RootPanel.get("errorLabelContainer").add(errorLabel); + + // Focus the cursor on the name field when the app loads + nameField.setFocus(true); + nameField.selectAll(); + + // Create the popup dialog box + final DialogBox dialogBox = new DialogBox(); + dialogBox.setText("Remote Procedure Call"); + dialogBox.setAnimationEnabled(true); + final Button closeButton = new Button("Close"); + // We can set the id of a widget by accessing its Element + closeButton.getElement().setId("closeButton"); + final Label textToServerLabel = new Label(); + final HTML serverResponseLabel = new HTML(); + VerticalPanel dialogVPanel = new VerticalPanel(); + dialogVPanel.addStyleName("dialogVPanel"); + dialogVPanel.add(new HTML("Sending name to the server:")); + dialogVPanel.add(textToServerLabel); + dialogVPanel.add(new HTML("
Server replies:")); + dialogVPanel.add(serverResponseLabel); + dialogVPanel.setHorizontalAlignment(VerticalPanel.ALIGN_RIGHT); + dialogVPanel.add(closeButton); + dialogBox.setWidget(dialogVPanel); + + // Add a handler to close the DialogBox + closeButton.addClickHandler(new ClickHandler() { + public void onClick(ClickEvent event) { + dialogBox.hide(); + sendButton.setEnabled(true); + sendButton.setFocus(true); + } + }); + + // Create a handler for the sendButton and nameField + class MyHandler implements ClickHandler, KeyUpHandler { + /** + * Fired when the user clicks on the sendButton. + */ + public void onClick(ClickEvent event) { + sendNameToServer(); + } + + /** + * Fired when the user types in the nameField. + */ + public void onKeyUp(KeyUpEvent event) { + if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) { + sendNameToServer(); + } + } + + /** + * Send the name from the nameField to the server and wait for a response. + */ + private void sendNameToServer() { + // First, we validate the input. + errorLabel.setText(""); + String textToServer = nameField.getText(); + if (!FieldVerifier.isValidName(textToServer)) { + errorLabel.setText("Please enter at least four characters"); + return; + } + + // Then, we send the input to the server. + sendButton.setEnabled(false); + textToServerLabel.setText(textToServer); + serverResponseLabel.setText(""); + greetingService.greetServer(textToServer, + new AsyncCallback() { + public void onFailure(Throwable caught) { + // Show the RPC error message to the user + dialogBox + .setText("Remote Procedure Call - Failure"); + serverResponseLabel + .addStyleName("serverResponseLabelError"); + serverResponseLabel.setHTML(SERVER_ERROR); + dialogBox.center(); + closeButton.setFocus(true); + } + + public void onSuccess(GreetingResponse result) { + dialogBox.setText("Remote Procedure Call"); + serverResponseLabel + .removeStyleName("serverResponseLabelError"); + serverResponseLabel.setHTML(new SafeHtmlBuilder() + .appendEscaped(result.getGreeting()) + .appendHtmlConstant("

I am running ") + .appendEscaped(result.getServerInfo()) + .appendHtmlConstant(".

It looks like you are using:
") + .appendEscaped(result.getUserAgent()) + .toSafeHtml()); + dialogBox.center(); + closeButton.setFocus(true); + } + }); + } + } + + // Add a handler to send the name to the server + MyHandler handler = new MyHandler(); + sendButton.addClickHandler(handler); + nameField.addKeyUpHandler(handler); + } +} diff --git a/gwtdemo-client/src/main/java/space/barrx/gwtdemo/MenuItemHandler.java b/gwtdemo-client/src/main/java/space/barrx/gwtdemo/MenuItemHandler.java new file mode 100644 index 0000000..662aecd --- /dev/null +++ b/gwtdemo-client/src/main/java/space/barrx/gwtdemo/MenuItemHandler.java @@ -0,0 +1,8 @@ +package space.barrx.gwtdemo; + +/** + * Handler for menu item actions. + */ +public interface MenuItemHandler { + void onMenuItemClicked(String itemLabel); +} diff --git a/gwtdemo-client/src/main/java/space/barrx/gwtdemo/MenuPresenter.java b/gwtdemo-client/src/main/java/space/barrx/gwtdemo/MenuPresenter.java new file mode 100644 index 0000000..e24c692 --- /dev/null +++ b/gwtdemo-client/src/main/java/space/barrx/gwtdemo/MenuPresenter.java @@ -0,0 +1,68 @@ +package space.barrx.gwtdemo; + +import com.google.gwt.core.client.GWT; + +/** + * Presenter for the Menu in MVP pattern. + * Handles menu logic and coordinates between the view and the application. + */ +public class MenuPresenter { + private MenuView view; + + public MenuPresenter(MenuView view) { + this.view = view; + view.setPresenter(this); + setupMenu(); + } + + /** + * Initialize the menu with default items. + */ + private void setupMenu() { + view.addMenuItem("Home", new MenuItemHandler() { + @Override + public void onMenuItemClicked(String itemLabel) { + onHomeClicked(); + } + }); + + view.addMenuItem("About", new MenuItemHandler() { + @Override + public void onMenuItemClicked(String itemLabel) { + onAboutClicked(); + } + }); + + view.addMenuItem("Settings", new MenuItemHandler() { + @Override + public void onMenuItemClicked(String itemLabel) { + onSettingsClicked(); + } + }); + } + + /** + * Handle Home menu item click. + */ + private void onHomeClicked() { + GWT.log("Home menu item clicked"); + } + + /** + * Handle About menu item click. + */ + private void onAboutClicked() { + GWT.log("About menu item clicked"); + } + + /** + * Handle Settings menu item click. + */ + private void onSettingsClicked() { + GWT.log("Settings menu item clicked"); + } + + public MenuView getView() { + return view; + } +} diff --git a/gwtdemo-client/src/main/java/space/barrx/gwtdemo/MenuView.java b/gwtdemo-client/src/main/java/space/barrx/gwtdemo/MenuView.java new file mode 100644 index 0000000..f1ca06b --- /dev/null +++ b/gwtdemo-client/src/main/java/space/barrx/gwtdemo/MenuView.java @@ -0,0 +1,18 @@ +package space.barrx.gwtdemo; + +import com.google.gwt.user.client.ui.IsWidget; + +/** + * View interface for the Menu in MVP pattern. + */ +public interface MenuView extends IsWidget { + /** + * Set the presenter that will handle menu actions. + */ + void setPresenter(MenuPresenter presenter); + + /** + * Add a menu item with the given label and action handler. + */ + void addMenuItem(String label, MenuItemHandler handler); +} diff --git a/gwtdemo-client/src/main/java/space/barrx/gwtdemo/MenuViewImpl.java b/gwtdemo-client/src/main/java/space/barrx/gwtdemo/MenuViewImpl.java new file mode 100644 index 0000000..770a77e --- /dev/null +++ b/gwtdemo-client/src/main/java/space/barrx/gwtdemo/MenuViewImpl.java @@ -0,0 +1,40 @@ +package space.barrx.gwtdemo; + +import com.google.gwt.safehtml.shared.SafeHtml; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.MenuBar; +import com.google.gwt.user.client.ui.MenuItem; + +/** + * Implementation of MenuView using GWT's MenuBar widget. + */ +public class MenuViewImpl extends Composite implements MenuView { + private MenuBar menu; + private MenuPresenter presenter; + + public MenuViewImpl() { + menu = new MenuBar(); + menu.addStyleName("mainMenu"); + initWidget(menu); + } + + @Override + public void setPresenter(MenuPresenter presenter) { + this.presenter = presenter; + } + + @Override + public void addMenuItem(String label, final MenuItemHandler handler) { + + MenuItem item = new MenuItem(new SafeHtml() { + + @Override + public String asString() { + return label; + } + + }); + item.setScheduledCommand(() -> handler.onMenuItemClicked(label)); + menu.addItem(item); + } +} diff --git a/gwtdemo-client/src/main/module.gwt.xml b/gwtdemo-client/src/main/module.gwt.xml new file mode 100644 index 0000000..6ed678f --- /dev/null +++ b/gwtdemo-client/src/main/module.gwt.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/gwtdemo-server/pom.xml b/gwtdemo-server/pom.xml new file mode 100644 index 0000000..e213e0f --- /dev/null +++ b/gwtdemo-server/pom.xml @@ -0,0 +1,91 @@ + + + 4.0.0 + + + space.barrx + gwtdemo + 1.0-SNAPSHOT + + + gwtdemo-server + war + + + + ${project.groupId} + gwtdemo-shared + ${project.version} + + + org.gwtproject + gwt-servlet-jakarta + + + jakarta.servlet + jakarta.servlet-api + provided + + + + + + + + org.eclipse.jetty.ee10 + jetty-ee10-maven-plugin + + 1 + ${basedir}/src/main/jettyconf/context.xml + + + + + + + + + + env-prod + + true + + + + ${project.groupId} + gwtdemo-client + ${project.version} + war + runtime + + + + + env-dev + + + env + dev + + + + + + + org.eclipse.jetty.ee10 + jetty-ee10-maven-plugin + + + + ${basedir}/src/main/webapp + ${project.build.directory}/gwt/launcherDir/ + + + + + + + + + + diff --git a/gwtdemo-server/src/main/java/space/barrx/gwtdemo/GreetingServiceImpl.java b/gwtdemo-server/src/main/java/space/barrx/gwtdemo/GreetingServiceImpl.java new file mode 100644 index 0000000..9828b03 --- /dev/null +++ b/gwtdemo-server/src/main/java/space/barrx/gwtdemo/GreetingServiceImpl.java @@ -0,0 +1,30 @@ +package space.barrx.gwtdemo; + +import com.google.gwt.user.server.rpc.jakarta.RemoteServiceServlet; + +/** + * The server side implementation of the RPC service. + */ +@SuppressWarnings("serial") +public class GreetingServiceImpl extends RemoteServiceServlet implements + GreetingService { + + public GreetingResponse greetServer(String input) throws IllegalArgumentException { + // Verify that the input is valid. + if (!FieldVerifier.isValidName(input)) { + // If the input is not valid, throw an IllegalArgumentException back to + // the client. + throw new IllegalArgumentException( + "Name must be at least 4 characters long"); + } + + GreetingResponse response = new GreetingResponse(); + + response.setServerInfo(getServletContext().getServerInfo()); + response.setUserAgent(getThreadLocalRequest().getHeader("User-Agent")); + + response.setGreeting("Hello, " + input + "!"); + + return response; + } +} diff --git a/gwtdemo-server/src/main/jettyconf/context.xml b/gwtdemo-server/src/main/jettyconf/context.xml new file mode 100644 index 0000000..95ca407 --- /dev/null +++ b/gwtdemo-server/src/main/jettyconf/context.xml @@ -0,0 +1,7 @@ + + + + org.eclipse.jetty.servlet.Default.useFileMappedBuffer + false + + diff --git a/gwtdemo-server/src/main/webapp/WEB-INF/web.xml b/gwtdemo-server/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..8c83d70 --- /dev/null +++ b/gwtdemo-server/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,22 @@ + + + + + + greetServlet + space.barrx.gwtdemo.GreetingServiceImpl + + + + greetServlet + /gwtapp/greet + + + + + index.html + + + diff --git a/gwtdemo-server/src/main/webapp/favicon.ico b/gwtdemo-server/src/main/webapp/favicon.ico new file mode 100644 index 0000000..858a707 Binary files /dev/null and b/gwtdemo-server/src/main/webapp/favicon.ico differ diff --git a/gwtdemo-server/src/main/webapp/gwtdemo.css b/gwtdemo-server/src/main/webapp/gwtdemo.css new file mode 100644 index 0000000..b814366 --- /dev/null +++ b/gwtdemo-server/src/main/webapp/gwtdemo.css @@ -0,0 +1,50 @@ +/** Add css rules here for your application. */ + + +/** Example rules used by the template application (remove for your app) */ +h1 { + font-size: 2em; + font-weight: bold; + color: #777777; + margin: 40px 0px 70px; + text-align: center; +} + +.sendButton { + display: block; + font-size: 16pt; +} + +/** Most GWT widgets already have a style name defined */ +.gwt-DialogBox { + width: 400px; +} + +.dialogVPanel { + margin: 5px; +} + +.serverResponseLabelError { + color: red; +} + +/** Set ids using widget.getElement().setId("idOfElement") */ +#closeButton { + margin: 15px 6px 6px; +} + +/** Menu styles */ +.mainMenu { + background-color: #f0f0f0; + border-bottom: 2px solid #333; + padding: 10px; +} + +.mainMenu .gwt-MenuItem { + padding: 5px 15px; + cursor: pointer; +} + +.mainMenu .gwt-MenuItem:hover { + background-color: #ddd; +} diff --git a/gwtdemo-server/src/main/webapp/index.html b/gwtdemo-server/src/main/webapp/index.html new file mode 100644 index 0000000..f6fda31 --- /dev/null +++ b/gwtdemo-server/src/main/webapp/index.html @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + Web Application Starter Project + + + + + + + + + + + + + + + + + + + + + +

Web Application Starter Project

+ + + + + + + + + + + + +
Please enter your name:
+ + diff --git a/gwtdemo-shared/pom.xml b/gwtdemo-shared/pom.xml new file mode 100644 index 0000000..ae364d5 --- /dev/null +++ b/gwtdemo-shared/pom.xml @@ -0,0 +1,29 @@ + + + 4.0.0 + + + space.barrx + gwtdemo + 1.0-SNAPSHOT + + + gwtdemo-shared + + + + org.gwtproject + gwt-servlet-jakarta + provided + + + + + + + org.apache.maven.plugins + maven-source-plugin + + + + diff --git a/gwtdemo-shared/src/main/java/space/barrx/gwtdemo/FieldVerifier.java b/gwtdemo-shared/src/main/java/space/barrx/gwtdemo/FieldVerifier.java new file mode 100644 index 0000000..cade152 --- /dev/null +++ b/gwtdemo-shared/src/main/java/space/barrx/gwtdemo/FieldVerifier.java @@ -0,0 +1,42 @@ +package space.barrx.gwtdemo; + +/** + *

+ * FieldVerifier validates that the name the user enters is valid. + *

+ *

+ * This class is in the shared project because we use it in both + * the client code and on the server. On the client, we verify that the name is + * valid before sending an RPC request so the user doesn't have to wait for a + * network round trip to get feedback. On the server, we verify that the name is + * correct to ensure that the input is correct regardless of where the RPC + * originates. + *

+ *

+ * When creating a class that is used on both the client and the server, be sure + * that all code is translatable and does not use native JavaScript. Code that + * is not translatable (such as code that interacts with a database or the file + * system) cannot be compiled into client side JavaScript. Code that uses native + * JavaScript (such as Widgets) cannot be run on the server. + *

+ */ +public class FieldVerifier { + + /** + * Verifies that the specified name is valid for our service. + * + * In this example, we only require that the name is at least four + * characters. In your application, you can use more complex checks to ensure + * that usernames, passwords, email addresses, URLs, and other fields have the + * proper syntax. + * + * @param name the name to validate + * @return true if valid, false if invalid + */ + public static boolean isValidName(String name) { + if (name == null) { + return false; + } + return name.length() > 3; + } +} diff --git a/gwtdemo-shared/src/main/java/space/barrx/gwtdemo/GreetingResponse.java b/gwtdemo-shared/src/main/java/space/barrx/gwtdemo/GreetingResponse.java new file mode 100644 index 0000000..0ec4fc7 --- /dev/null +++ b/gwtdemo-shared/src/main/java/space/barrx/gwtdemo/GreetingResponse.java @@ -0,0 +1,34 @@ +package space.barrx.gwtdemo; + +import java.io.Serializable; + +@SuppressWarnings("serial") +public class GreetingResponse implements Serializable { + private String greeting; + private String serverInfo; + private String userAgent; + + public String getGreeting() { + return greeting; + } + + public void setGreeting(String greeting) { + this.greeting = greeting; + } + + public String getServerInfo() { + return serverInfo; + } + + public void setServerInfo(String serverInfo) { + this.serverInfo = serverInfo; + } + + public String getUserAgent() { + return userAgent; + } + + public void setUserAgent(String userAgent) { + this.userAgent = userAgent; + } +} \ No newline at end of file diff --git a/gwtdemo-shared/src/main/java/space/barrx/gwtdemo/GreetingService.java b/gwtdemo-shared/src/main/java/space/barrx/gwtdemo/GreetingService.java new file mode 100644 index 0000000..d8323d2 --- /dev/null +++ b/gwtdemo-shared/src/main/java/space/barrx/gwtdemo/GreetingService.java @@ -0,0 +1,12 @@ +package space.barrx.gwtdemo; + +import com.google.gwt.user.client.rpc.RemoteService; +import com.google.gwt.user.client.rpc.RemoteServiceRelativePath; + +/** + * The client side stub for the RPC service. + */ +@RemoteServiceRelativePath("greet") +public interface GreetingService extends RemoteService { + GreetingResponse greetServer(String name) throws IllegalArgumentException; +} diff --git a/gwtdemo-shared/src/main/java/space/barrx/gwtdemo/GreetingServiceAsync.java b/gwtdemo-shared/src/main/java/space/barrx/gwtdemo/GreetingServiceAsync.java new file mode 100644 index 0000000..ef2670f --- /dev/null +++ b/gwtdemo-shared/src/main/java/space/barrx/gwtdemo/GreetingServiceAsync.java @@ -0,0 +1,10 @@ +package space.barrx.gwtdemo; + +import com.google.gwt.user.client.rpc.AsyncCallback; + +/** + * The async counterpart of GreetingService. + */ +public interface GreetingServiceAsync { + void greetServer(String input, AsyncCallback callback); +} diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..81194e5 --- /dev/null +++ b/pom.xml @@ -0,0 +1,104 @@ + + 4.0.0 + space.barrx + gwtdemo + 1.0-SNAPSHOT + pom + + UTF-8 + + + + + org.gwtproject + gwt + 2.13.0 + pom + import + + + jakarta.servlet + jakarta.servlet-api + 6.0.0 + + + + + + + net.ltgt.gwt.maven + gwt-maven-plugin + false + + ${basedir}/gwtdemo-server/target/gwt/launcherDir + + ${basedir}/gwtdemo-server/target/gwt/launcherDir + + -noserver + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.15.0 + + 17 + + + + org.apache.maven.plugins + maven-resources-plugin + 3.4.0 + + + org.apache.maven.plugins + maven-surefire-plugin + 3.5.4 + + + org.apache.maven.plugins + maven-war-plugin + 3.5.1 + + + org.eclipse.jetty.ee10 + jetty-ee10-maven-plugin + 12.1.6 + + + net.ltgt.gwt.maven + gwt-maven-plugin + 1.2.0 + true + + 17 + true + + + + org.apache.maven.plugins + maven-source-plugin + 3.4.0 + + + attach-sources + package + + jar-no-fork + + + + + + + + + gwtdemo-client + gwtdemo-shared + gwtdemo-server + +