diff --git a/config/pmd-suppressions.properties b/config/pmd-suppressions.properties index c1361e2151f9cb1865eae99ea0a17f72aba74503..11439c609a026b20bde6d2eed37aa7a4777cabd7 100644 --- a/config/pmd-suppressions.properties +++ b/config/pmd-suppressions.properties @@ -1,11 +1,13 @@ # See https://maven.apache.org/plugins/maven-pmd-plugin/examples/violation-exclusions.html # annotations generate not clean code +fr.agrometinfo.www.server.dao.RegionDaoHibernate=UselessOverridingMethod fr.agrometinfo.www.shared.dto.ChoiceDTOBeanJsonDeserializerImpl=UnnecessaryImport fr.agrometinfo.www.shared.dto.ChoiceDTOBeanJsonSerializerImpl=UnnecessaryImport fr.agrometinfo.www.shared.dto.ChoiceDTO_MapperImpl=UnnecessaryImport fr.agrometinfo.www.shared.dto.IndicatorDTOBeanJsonDeserializerImpl=UnnecessaryImport fr.agrometinfo.www.shared.dto.IndicatorDTOBeanJsonSerializerImpl=UnnecessaryImport fr.agrometinfo.www.shared.dto.IndicatorDTO_MapperImpl=UnnecessaryImport +fr.agrometinfo.www.shared.dto.MessageDTOBeanJsonSerializerImpl=UnnecessaryImport fr.agrometinfo.www.shared.dto.PeriodDTOBeanJsonDeserializerImpl=UnnecessaryImport fr.agrometinfo.www.shared.dto.PeriodDTOBeanJsonSerializerImpl=UnnecessaryImport fr.agrometinfo.www.shared.dto.PeriodDTO_MapperImpl=UnnecessaryImport @@ -14,6 +16,7 @@ fr.agrometinfo.www.shared.dto.SimpleFeatureBeanJsonSerializerImpl=UnnecessaryImp fr.agrometinfo.www.shared.dto.SummaryDTOBeanJsonDeserializerImpl=UnnecessaryImport fr.agrometinfo.www.shared.dto.SummaryDTOBeanJsonSerializerImpl=UnnecessaryImport fr.agrometinfo.www.shared.dto.SummaryDTO_MapperImpl=UnnecessaryImport +fr.agrometinfo.www.shared.service.ApplicationServiceFactory=UnnecessaryImport fr.agrometinfo.www.shared.service.IndicatorServiceFactory=UnnecessaryImport org.geojson.FeatureBeanJsonDeserializerImpl=UnnecessaryImport org.geojson.FeatureBeanJsonSerializerImpl=UnnecessaryImport @@ -31,5 +34,3 @@ org.geojson.MultiPolygon_MapperImpl=UnnecessaryImport org.geojson.PolygonBeanJsonDeserializerImpl=UnnecessaryImport org.geojson.PolygonBeanJsonSerializerImpl=UnnecessaryImport org.geojson.Polygon_MapperImpl=UnnecessaryImport -# wrong positive -fr.agrometinfo.www.server.dao.RegionDaoHibernate=UselessOverridingMethod diff --git a/www-client/src/main/java/fr/agrometinfo/www/client/i18n/AppConstants.java b/www-client/src/main/java/fr/agrometinfo/www/client/i18n/AppConstants.java index 238bb3fc04aad5679757a093e93a607ca611021b..d1e39992a45f7808c8dddbb88795f4480e77a2c5 100644 --- a/www-client/src/main/java/fr/agrometinfo/www/client/i18n/AppConstants.java +++ b/www-client/src/main/java/fr/agrometinfo/www/client/i18n/AppConstants.java @@ -172,6 +172,12 @@ public interface AppConstants extends com.google.gwt.i18n.client.ConstantsWithLo @DefaultStringValue("HTTP status text:") String failureStatusText(); + /** + * @return translation + */ + @DefaultStringValue("Invalid e-mail address") + String invalidEmailAddress(); + /** * @return translation */ @@ -228,6 +234,12 @@ public interface AppConstants extends com.google.gwt.i18n.client.ConstantsWithLo @DefaultStringValue("Other AgroClim's services and tools") String otherAgroclimApps(); + /** + * @return translation + */ + @DefaultStringValue("* This field is required.") + String requiredErrorMessage(); + /** * @return translation */ diff --git a/www-client/src/main/java/fr/agrometinfo/www/client/i18n/AppMessages.java b/www-client/src/main/java/fr/agrometinfo/www/client/i18n/AppMessages.java index 4ef2fbb67af7ff9ea85982d5191c599e6dd6773d..f6cbd88b70f0cde3a1fa578ded409bc3d44ca1f6 100644 --- a/www-client/src/main/java/fr/agrometinfo/www/client/i18n/AppMessages.java +++ b/www-client/src/main/java/fr/agrometinfo/www/client/i18n/AppMessages.java @@ -59,8 +59,8 @@ public interface AppMessages extends Messages { * @param details failure details * @return translation */ - @DefaultMessage("Failed to retrieve periods: {0}.") - String failureRetrievePeriods(String details); + @DefaultMessage("Failed to communicate with server: {0}.") + String failureResponse(String details); /** * @param statusCode HTTP status code diff --git a/www-client/src/main/java/fr/agrometinfo/www/client/presenter/ContactPresenter.java b/www-client/src/main/java/fr/agrometinfo/www/client/presenter/ContactPresenter.java index 1b3640d856713243a09493f01b4302c999bb1f30..834a028085b924da1d58124fb575ccca6b945bf9 100644 --- a/www-client/src/main/java/fr/agrometinfo/www/client/presenter/ContactPresenter.java +++ b/www-client/src/main/java/fr/agrometinfo/www/client/presenter/ContactPresenter.java @@ -1,7 +1,17 @@ package fr.agrometinfo.www.client.presenter; +import java.util.StringJoiner; + +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.Window.Navigator; + +import fr.agrometinfo.www.client.util.ApplicationUtils; +import fr.agrometinfo.www.client.util.UiUtils; import fr.agrometinfo.www.client.view.BaseView; import fr.agrometinfo.www.client.view.ContactView; +import fr.agrometinfo.www.client.view.LayoutView; +import fr.agrometinfo.www.shared.dto.MessageDTO; +import fr.agrometinfo.www.shared.service.ApplicationServiceFactory; /** * Presenter related to contact form. @@ -14,12 +24,63 @@ public final class ContactPresenter implements Presenter { * Interface for the related view. */ public interface View extends BaseView<ContactPresenter> { + /** + * Close modal. + */ + void close(); + } + + /** + * The layout handling the panel. + */ + private LayoutView layoutView; + + /** + * Related view. + */ + private ContactView view; + + /** + * Send an e-mail to AgroMetInfo team. + * + * @param name user's name + * @param emailAddress user's e-mail address + * @param message message + */ + public void send(final String name, final String emailAddress, final String message) { + final StringJoiner sj = new StringJoiner("\n"); + sj.add(message); + sj.add(""); + sj.add("----"); + sj.add("URL : " + Window.Location.getHref()); + sj.add("Application version : " + ApplicationUtils.getVersion()); + sj.add("Browser : " + UiUtils.getRealBrowserName()); + sj.add("UserAgent : " + Navigator.getUserAgent()); + sj.add("OS : " + Navigator.getPlatform()); + sj.add("Browser size : " + Window.getClientWidth() + "x" + Window.getClientHeight()); + sj.add("Screen size : " + UiUtils.getScreenWidth() + "x" + UiUtils.getScreenHeight()); + final MessageDTO dto = new MessageDTO(); + dto.setName(name); + dto.setEmailAddress(emailAddress); + dto.setMessage(sj.toString()); + ApplicationServiceFactory.INSTANCE// + .sendMessage(dto) // + .onSuccess(v -> view.close()) // + .onFailed(layoutView::failureNotification) // + .send(); + } + + /** + * @param lView the layout handling the panel. + */ + public void setLayoutView(final LayoutView lView) { + layoutView = lView; } @Override public void start() { - final ContactView view = new ContactView(); + view = new ContactView(); view.setPresenter(this); view.init(); } diff --git a/www-client/src/main/java/fr/agrometinfo/www/client/presenter/LayoutPresenter.java b/www-client/src/main/java/fr/agrometinfo/www/client/presenter/LayoutPresenter.java index 868d4e3c08e80233bddb860c028a36108d9353e8..b58c8e14c07f236af38409c4f99bdb2d792aaa14 100644 --- a/www-client/src/main/java/fr/agrometinfo/www/client/presenter/LayoutPresenter.java +++ b/www-client/src/main/java/fr/agrometinfo/www/client/presenter/LayoutPresenter.java @@ -170,7 +170,9 @@ public final class LayoutPresenter implements Presenter { * Display contact form. */ public void showContactView() { - new ContactPresenter().start(); + final ContactPresenter presenter = new ContactPresenter(); + presenter.setLayoutView(view); + presenter.start(); } @Override diff --git a/www-client/src/main/java/fr/agrometinfo/www/client/util/UiUtils.java b/www-client/src/main/java/fr/agrometinfo/www/client/util/UiUtils.java index 1b35920f8fb05976a5cf798ad164046e3d2c038c..db182cbc5a43177d8d40f62cd63a57d96d5680f9 100644 --- a/www-client/src/main/java/fr/agrometinfo/www/client/util/UiUtils.java +++ b/www-client/src/main/java/fr/agrometinfo/www/client/util/UiUtils.java @@ -6,6 +6,39 @@ package fr.agrometinfo.www.client.util; * @author Olivier Maury */ public final class UiUtils { + /** + * @return the name of the used browser. + */ + public static native String getBrowserName() /*-{ + return navigator.userAgent.toLowerCase(); + }-*/; + + /** + * @return browser name, not JavaScript value from navigator.*name. + */ + public static String getRealBrowserName() { + if (isChromeBrowser()) { + return "Chrome"; + } + if (isFirefoxBrowser()) { + return "Firefox"; + } + if (isIEBrowser()) { + return "Internet Explorer"; + } + if (isOperaBrowser()) { + return "Opera"; + } + return "Unknown"; + } + + /** + * @return screen height (px) + */ + public static native int getScreenHeight() /*-{ + return screen.height; + }-*/; + /** * @return screen width (px) */ @@ -13,6 +46,35 @@ public final class UiUtils { return screen.width; }-*/; + /** + * @return true if the current browser is Chrome. + */ + public static boolean isChromeBrowser() { + return getBrowserName().toLowerCase().contains("chrome") && !getBrowserName().toLowerCase().contains("opr"); + } + + /** + * @return true if the current browser is Firefox. + */ + public static boolean isFirefoxBrowser() { + return getBrowserName().toLowerCase().contains("firefox"); + } + + /** + * @return true if the current browser is IE (Internet Explorer). + */ + public static boolean isIEBrowser() { + return getBrowserName().toLowerCase().contains("msie"); + } + + /** + * @return true if the current browser is Opera. + */ + public static boolean isOperaBrowser() { + return getBrowserName().toLowerCase().contains("opr"); + } + + /** * No Constructor. */ diff --git a/www-client/src/main/java/fr/agrometinfo/www/client/view/ContactView.java b/www-client/src/main/java/fr/agrometinfo/www/client/view/ContactView.java index 89e620fb6c02bfd7d04411367d55e655ef24c462..d406b65eeb6a0c1649bd9d854c9664cef42219d6 100644 --- a/www-client/src/main/java/fr/agrometinfo/www/client/view/ContactView.java +++ b/www-client/src/main/java/fr/agrometinfo/www/client/view/ContactView.java @@ -4,14 +4,17 @@ import static org.jboss.elemento.Elements.a; import static org.jboss.elemento.Elements.span; import org.dominokit.domino.ui.button.Button; +import org.dominokit.domino.ui.forms.EmailBox; import org.dominokit.domino.ui.forms.TextArea; import org.dominokit.domino.ui.forms.TextBox; import org.dominokit.domino.ui.icons.Icons; import org.dominokit.domino.ui.modals.ModalDialog; import org.dominokit.domino.ui.utils.TextNode; +import org.jboss.elemento.HtmlContentBuilder; import com.google.gwt.core.client.GWT; +import elemental2.dom.HTMLAnchorElement; import fr.agrometinfo.www.client.i18n.AppConstants; import fr.agrometinfo.www.client.presenter.ContactPresenter; @@ -32,45 +35,76 @@ public final class ContactView extends AbstractBaseView<ContactPresenter> implem */ private ModalDialog modal; + /** + * Input box for user's name. + */ + private TextBox nameTextBox; + + /** + * Input box for user's e-mail address. + */ + private EmailBox emailBox; + + /** + * Input box for user's message. + */ + private TextArea bodyTextBox; + + @Override + public void close() { + modal.close(); + } + @Override public void init() { GWT.log("ContactView.init()"); - final TextBox nameTextBox = TextBox.create(CSTS.contactNameField()) + nameTextBox = TextBox.create(CSTS.contactNameField()) .setRequired(true) + .setRequiredErrorMessage(CSTS.requiredErrorMessage()) .setAutoValidation(true) .setFixErrorsPosition(true) .addLeftAddOn(Icons.ALL.label()); - final TextBox emailTextBox = TextBox.create(CSTS.contactEmailField()) + emailBox = EmailBox.create(CSTS.contactEmailField()) .setRequired(true) + .setRequiredErrorMessage(CSTS.requiredErrorMessage()) + .setTypeMismatchErrorMessage(CSTS.invalidEmailAddress()) .setAutoValidation(true) .setFixErrorsPosition(true) .addLeftAddOn(Icons.ALL.email()); - final TextArea bodyTextBox = TextArea.create(CSTS.contactMessageField()) // + bodyTextBox = TextArea.create(CSTS.contactMessageField()) // .setRequired(true) + .setRequiredErrorMessage(CSTS.requiredErrorMessage()) .setAutoValidation(true) .setFixErrorsPosition(true) .addLeftAddOn(Icons.ALL.textarea_mdi()) .setPlaceholder(CSTS.contactMessagePlaceHolder()); + final HtmlContentBuilder<HTMLAnchorElement> seeFaq = a(".").attr("target", "_blank") // + .attr("href", "../explications.html").textContent(CSTS.contactSeeFAQ()); + final HtmlContentBuilder<HTMLAnchorElement> privacy = a(".").attr("target", "_blank") // + .attr("href", "../privacy.html").textContent(CSTS.seePrivacyPolicy()); modal = ModalDialog.create(CSTS.contactUs()) .setAutoClose(true) // - .appendChild(a(".").textContent(CSTS.contactSeeFAQ())) // + .appendChild(seeFaq) // .appendChild(span().textContent(" ")) // .appendChild(TextNode.of(CSTS.contactSeeFAQ2())) // .appendChild(nameTextBox) // - .appendChild(emailTextBox) // + .appendChild(emailBox) // .appendChild(bodyTextBox) // .appendChild(TextNode.of(CSTS.contactDataGathering())) // .appendChild(span().textContent(" ")) // - .appendChild(a(".").textContent(CSTS.seePrivacyPolicy())) // + .appendChild(privacy) // .appendFooterChild(Button.create(CSTS.cancel()).linkify() // .addClickListener(e -> modal.close())) // .appendFooterChild(Button.create(CSTS.contactSendMessage()).linkify() // - .addClickListener(e -> this.onSendClick()) // - ).open(); + .addClickListener(e -> this.onSendClick())) // + .open(); } private void onSendClick() { GWT.log("ContactView.onSendClick()"); - modal.close(); + if (!nameTextBox.validate().isValid() || !emailBox.validate().isValid() || !bodyTextBox.validate().isValid()) { + return; + } + getPresenter().send(nameTextBox.getValue(), emailBox.getValue(), bodyTextBox.getValue()); } } diff --git a/www-client/src/main/java/fr/agrometinfo/www/client/view/LayoutView.java b/www-client/src/main/java/fr/agrometinfo/www/client/view/LayoutView.java index c01b1b5f8f7cfb17c9513661d53953103d5c38b8..f93e4d1e24510efe7ac234298d397d73852a0778 100644 --- a/www-client/src/main/java/fr/agrometinfo/www/client/view/LayoutView.java +++ b/www-client/src/main/java/fr/agrometinfo/www/client/view/LayoutView.java @@ -186,7 +186,7 @@ public final class LayoutView extends AbstractBaseView<LayoutPresenter> implemen @Override public void failureNotification(final FailedResponseBean failedResponse) { - this.notification(MSGS.failureRetrievePeriods(getDetails(failedResponse))); + this.notification(MSGS.failureResponse(getDetails(failedResponse))); } /** diff --git a/www-client/src/main/resources/fr/agrometinfo/www/client/i18n/AppConstants_fr.properties b/www-client/src/main/resources/fr/agrometinfo/www/client/i18n/AppConstants_fr.properties index e744828152a53b94eb415758cab3081bb1b308ad..a027dbb4eb1dd4066645a0da69282bb2cc65acdb 100644 --- a/www-client/src/main/resources/fr/agrometinfo/www/client/i18n/AppConstants_fr.properties +++ b/www-client/src/main/resources/fr/agrometinfo/www/client/i18n/AppConstants_fr.properties @@ -26,6 +26,7 @@ dailyValues = Valeurs journalières failureBody = Corps : failureHeaders = Entêtes HTTP : failureStatusText = Texte d’état HTTP : +invalidEmailAddress = Adresse courriel invalide login = Se connecter loginOrSignIn = ou s’inscrire avec logout = Se déconnecter @@ -35,6 +36,7 @@ no= Non normalComparison= Comparaison à la normale normalComparisonTooltip= <b>La comparaison à la normale</b> se calcule en soustrayant <b>la moyenne de l’indicateur choisi</b> pour les trente dernières années (1990-2020) de <b>l’année sélectionnée</b>. otherAgroclimApps = Autres services et outils d’AgroClim +requiredErrorMessage = * Ce champ est obligatoire. seePrivacyPolicy = Consultez le paragraphe « Données personnelles » dans les mentions légales. toggleRightPanel = Afficher / masquer le panneau de droite userProfile = Compte et paramètres diff --git a/www-client/src/main/resources/fr/agrometinfo/www/client/i18n/AppMessages_fr.properties b/www-client/src/main/resources/fr/agrometinfo/www/client/i18n/AppMessages_fr.properties index 354ca34bd3362276c381f3843fa869f7b742ee27..2505c7800fff5cad4721eeb5a45fd494b27e6611 100644 --- a/www-client/src/main/resources/fr/agrometinfo/www/client/i18n/AppMessages_fr.properties +++ b/www-client/src/main/resources/fr/agrometinfo/www/client/i18n/AppMessages_fr.properties @@ -4,6 +4,7 @@ averageValue = Valeur moyenne de l’indicateur {0} ({1}) en {2} sur {3} cell = Maille n°{0} chartSubtitle = {0,date,medium}. Unité : {1} comparedValue = Écart moyen de la valeur de l’indicateur {0} ({1}) en {2} sur {3} +failureResponse = La communication avec le serveur a échoué : {0} failureStatusCode = Code d’état HTTP : {0} nbOfIndicatorPeriods[\=0] = Aucune période. nbOfIndicatorPeriods[\=1] = Une période. diff --git a/www-server/src/main/java/fr/agrometinfo/www/server/AgroMetInfoConfiguration.java b/www-server/src/main/java/fr/agrometinfo/www/server/AgroMetInfoConfiguration.java index 78b496fd3892e260d19091f9622a7c4ec269569f..9e95b3a7f50f990a88bb30955c8767f658199d38 100644 --- a/www-server/src/main/java/fr/agrometinfo/www/server/AgroMetInfoConfiguration.java +++ b/www-server/src/main/java/fr/agrometinfo/www/server/AgroMetInfoConfiguration.java @@ -62,7 +62,11 @@ public class AgroMetInfoConfiguration { /** * SMTP password. */ - SMTP_PASSWORD("smtp.password"); + SMTP_PASSWORD("smtp.password"), + /** + * E-mail address for support. + */ + SUPPORT_EMAIL("support.email"); /** * Key in config.properties. diff --git a/www-server/src/main/java/fr/agrometinfo/www/server/rs/ApplicationConfig.java b/www-server/src/main/java/fr/agrometinfo/www/server/rs/ApplicationConfig.java index 1e63fd974adb29e0f543910b4aec0fce27a82319..26fef07458cd2a65255a88a869f67a0263d17ced 100644 --- a/www-server/src/main/java/fr/agrometinfo/www/server/rs/ApplicationConfig.java +++ b/www-server/src/main/java/fr/agrometinfo/www/server/rs/ApplicationConfig.java @@ -43,7 +43,7 @@ public class ApplicationConfig extends Application { // Jackson configuration JacksonConfig.class, // JAX-RS resources - GeometryResource.class, IndicatorResource.class, UserResource.class, + ApplicationResource.class, GeometryResource.class, IndicatorResource.class, UserResource.class, // POJO // Dependencies of resources LogFilter.class diff --git a/www-server/src/main/java/fr/agrometinfo/www/server/rs/ApplicationResource.java b/www-server/src/main/java/fr/agrometinfo/www/server/rs/ApplicationResource.java new file mode 100644 index 0000000000000000000000000000000000000000..7fdbada1a3ed66d90b6d811b52984e1bdbe77e74 --- /dev/null +++ b/www-server/src/main/java/fr/agrometinfo/www/server/rs/ApplicationResource.java @@ -0,0 +1,77 @@ +package fr.agrometinfo.www.server.rs; + +import fr.agrometinfo.www.server.exception.AgroMetInfoException; +import fr.agrometinfo.www.server.service.MailService; +import fr.agrometinfo.www.server.service.MailService.Mail; +import fr.agrometinfo.www.server.util.ST; +import fr.agrometinfo.www.server.util.ST.Key; +import fr.agrometinfo.www.shared.dto.MessageDTO; +import fr.agrometinfo.www.shared.service.ApplicationService; +import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import lombok.extern.log4j.Log4j2; + +/** + * Resource to handle application end points. + * + * @author Olivier Maury + */ +@Log4j2 +@Path(ApplicationService.PATH) +@RequestScoped +public class ApplicationResource implements ApplicationService { + /** + * E-mail template. + */ + private static final String TEMPLATE = """ + nom : + ===== + <NAME> + + adresse courriel : + ================== + <EMAIL> + + message : + ========= + <MESSAGE> + """; + /** + * Convert DTO from "Contact Us" form to Mail for MailService. + * + * @param message dto to convert + * @return Mail for MailService + */ + private static Mail toMail(final MessageDTO message) { + final ST st = new ST(TEMPLATE); + st.add(Key.EMAIL, message.getEmailAddress()); + st.add(Key.MESSAGE, message.getMessage()); + st.add(Key.NAME, message.getName()); + final Mail mail = new Mail(); + mail.setContent(st.render()); + mail.setSubject("Formulaire de contact"); + return mail; + } + + /** + * Mail service. + */ + @Inject + private MailService mailService; + + @POST + @Path(ApplicationService.PATH_SEND_EMAIL) + @Override + public String sendMessage(final MessageDTO message) { + LOGGER.traceEntry(); + try { + mailService.send(toMail(message)); + return "Ok"; + } catch (final AgroMetInfoException e) { + return e.getLocalizedMessage(); + } + } + +} diff --git a/www-server/src/main/java/fr/agrometinfo/www/server/service/MailService.java b/www-server/src/main/java/fr/agrometinfo/www/server/service/MailService.java index 4378252302bbd9e740b45fb4f5dd0f3d798fffe1..f3f025a93096a0b1717f5f0c1562a41731f4b4cf 100644 --- a/www-server/src/main/java/fr/agrometinfo/www/server/service/MailService.java +++ b/www-server/src/main/java/fr/agrometinfo/www/server/service/MailService.java @@ -26,6 +26,8 @@ import fr.agrometinfo.www.server.exception.AgroMetInfoException; import fr.agrometinfo.www.server.exception.ErrorCategory; import fr.agrometinfo.www.server.exception.ErrorType; import jakarta.annotation.PostConstruct; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; import jakarta.mail.Address; import jakarta.mail.Authenticator; import jakarta.mail.Message; @@ -39,8 +41,6 @@ import jakarta.mail.internet.MimeMessage; import lombok.Data; import lombok.Getter; import lombok.extern.log4j.Log4j2; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; /** * Service to send e-mails. @@ -99,14 +99,13 @@ public class MailService { @Override public void append(final LogEvent event) { - Mail mail = new Mail(); - mail.setFromAddress("agrometinfo@inrae.fr"); - mail.setSubject("AgroMetInfo - error log"); + final Mail mail = new Mail(); + mail.setSubject("error log"); mail.setContent(super.toSerializable(event).toString()); mail.setToAddresses(List.of(configuration.get(ConfigurationKey.LOG_EMAIL))); try { send(mail); - } catch (AgroMetInfoException e) { + } catch (final AgroMetInfoException e) { // do nothing LOGGER.info("Cannot send email: {}", e.getMessage()); } @@ -177,7 +176,7 @@ public class MailService { */ @PostConstruct public void init() { - Properties props = new Properties(); + final Properties props = new Properties(); props.put("mail.smtp.auth", "true"); props.put("mail.smtp.host", configuration.get(ConfigurationKey.SMTP_HOST)); props.put("mail.smtp.port", configuration.get(ConfigurationKey.SMTP_PORT)); @@ -206,7 +205,7 @@ public class MailService { final Appender appender = new MailAppender(null, layout, true); appender.start(); config.addAppender(appender); - LoggerConfig loggerConfig = LoggerConfig.newBuilder() // + final LoggerConfig loggerConfig = LoggerConfig.newBuilder() // .withAdditivity(false) // .withConfig(config) // .withLevel(Level.ERROR) // @@ -235,13 +234,13 @@ public class MailService { try { subject = message.getSubject(); recipients = toString(message.getAllRecipients()); - } catch (MessagingException e) { + } catch (final MessagingException e) { throw new AgroMetInfoException(MailErrorType.DETAILS, e.getMessage()); } try { Transport.send(message); - } catch (MessagingException e) { - if (e instanceof SendFailedException sfe) { + } catch (final MessagingException e) { + if (e instanceof final SendFailedException sfe) { final Address[] invalid = sfe.getInvalidAddresses(); if (invalid != null) { throw new AgroMetInfoException(MailErrorType.SEND_FAILED_INVALID, subject, toString(invalid)); @@ -255,6 +254,22 @@ public class MailService { } } + /** + * Send an e-mail to log e-mail address at application start. + */ + public void sendApplicationStarted() { + final Mail mail = new Mail(); + mail.setContent("Démarrage de AgroMetInfo-www : " + LocalDateTime.now()); + mail.setFromAddress(configuration.get(ConfigurationKey.APP_EMAIL)); + mail.setSubject("Démarrage"); + mail.setToAddresses(List.of(configuration.get(ConfigurationKey.LOG_EMAIL))); + try { + send(mail); + } catch (final AgroMetInfoException e) { + LOGGER.info("failed to send e-mail : {}", e); + } + } + /** * @param mail text message to convert * @return jakarta converted message @@ -264,9 +279,15 @@ public class MailService { final String environment = configuration.get(ConfigurationKey.ENVIRONMENT); try { final Message message = new MimeMessage(session); + if (mail.getFromAddress() == null) { + mail.setFromAddress(configuration.get(ConfigurationKey.APP_EMAIL)); + } message.setFrom(new InternetAddress(mail.getFromAddress())); - List<InternetAddress> to = new ArrayList<>(); - for (String addr : mail.getToAddresses()) { + final List<InternetAddress> to = new ArrayList<>(); + if (mail.getToAddresses() == null || mail.getToAddresses().isEmpty()) { + mail.setToAddresses(List.of(configuration.get(ConfigurationKey.SUPPORT_EMAIL))); + } + for (final String addr : mail.getToAddresses()) { to.add(new InternetAddress(addr)); } message.setRecipients(Message.RecipientType.TO, to.toArray(InternetAddress[]::new)); @@ -275,10 +296,11 @@ public class MailService { } else { message.setSubject("AgroMetInfo " + environment + " : " + mail.getSubject()); } - message.setContent(mail.getContent(), "text/plain; charset=UTF-8"); + final String content = mail.getContent() + "\n-- \n" + configuration.get(ConfigurationKey.APP_URL); + message.setContent(content, "text/plain; charset=UTF-8"); message.setHeader("X-Environment", environment); return message; - } catch (MessagingException e) { + } catch (final MessagingException e) { throw new AgroMetInfoException(MailErrorType.DETAILS, e.getLocalizedMessage()); } } @@ -289,26 +311,9 @@ public class MailService { */ private String toString(final Address[] addresses) { final StringJoiner sj = new StringJoiner(", "); - for (Address addr : addresses) { + for (final Address addr : addresses) { sj.add(addr.toString()); } return sj.toString(); } - - /** - * Send an e-mail to log e-mail address at application start. - */ - public void sendApplicationStarted() { - Mail mail = new Mail(); - mail.setContent("Démarrage de AgroMetInfo-www : " + LocalDateTime.now() + "\n-- \n" - + configuration.get(ConfigurationKey.APP_URL)); - mail.setFromAddress(configuration.get(ConfigurationKey.APP_EMAIL)); - mail.setSubject("Démarrage"); - mail.setToAddresses(List.of(configuration.get(ConfigurationKey.LOG_EMAIL))); - try { - send(mail); - } catch (AgroMetInfoException e) { - LOGGER.info("failed to send e-mail : {}", e); - } - } } diff --git a/www-server/src/main/java/fr/agrometinfo/www/server/util/ST.java b/www-server/src/main/java/fr/agrometinfo/www/server/util/ST.java new file mode 100644 index 0000000000000000000000000000000000000000..fb7715b786a0441062634fb7841bda14099bc3f3 --- /dev/null +++ b/www-server/src/main/java/fr/agrometinfo/www/server/util/ST.java @@ -0,0 +1,86 @@ +package fr.agrometinfo.www.server.util; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import lombok.RequiredArgsConstructor; + +/** + * String template. + * + * @author Olivier Maury + */ +@RequiredArgsConstructor +public class ST { + /** + * Key replacement. + */ + public enum Key { + /** + * User's name. + */ + NAME, + /** + * User's e-mail. + */ + EMAIL, + /** + * User's message. + */ + MESSAGE; + } + + /** + * The template with string to be replaced. + */ + private final String template; + + /** + * The strings to replace. + */ + private final Map<String, String> replacements = new HashMap<>(); + + /** + * Inject an attribute (name/value pair). + * + * Return {@code this} so we can chain:<br> + * {@code t.add("x", 1).add("y", "hi")} + * + * @param key key (without < >) + * @param value value to replace + * @return instance + */ + public ST add(final Key key, final Object value) { + replacements.put(key.name(), String.valueOf(value)); + return this; + } + + /** + * Inject an attribute (name/value pair). + * + * Return {@code this} so we can chain:<br> + * {@code t.add("x", 1).add("y", "hi")} + * + * @param key key (without < >) + * @param value value to replace + * @return instance + */ + public ST add(final String key, final Object value) { + replacements.put(key, String.valueOf(value)); + return this; + } + + /** + * Render the template with replaced strings. + * + * @return rendered template + */ + public String render() { + String replaced = template; + for (final Entry<String, String> entry : replacements.entrySet()) { + replaced = replaced.replace("<" + entry.getKey() + ">", entry.getValue()); + } + return replaced; + } +} diff --git a/www-server/src/main/tomcat10xconf/context.xml b/www-server/src/main/tomcat10xconf/context.xml index 197593d809e7121ac017bb17a6b1a607ab8d0080..29874ece5ae94d1a17d751845c4aebba83d05866 100644 --- a/www-server/src/main/tomcat10xconf/context.xml +++ b/www-server/src/main/tomcat10xconf/context.xml @@ -10,6 +10,7 @@ <Parameter name="agrometinfo.smtp.port" value="587" /> <Parameter name="agrometinfo.smtp.user" value="agrometinfoXXXX" /> <Parameter name="agrometinfo.smtp.password" value="XXXX" /> + <Parameter name="agrometinfo.support.email" value="agrometinfoXXXX@inrae.fr" /> <Parameter name="sava.key" value="XXXX" /> <Parameter name="sava.pass" value="XXXX" /> <Resource diff --git a/www-shared/config/pmd-suppressions.properties b/www-shared/config/pmd-suppressions.properties index a9ac651a732b9d4b82e53b40c3e2b905659b7cc8..35388de1e41ddfd8384f40555527bb8d19c78f1b 100644 --- a/www-shared/config/pmd-suppressions.properties +++ b/www-shared/config/pmd-suppressions.properties @@ -6,6 +6,7 @@ fr.agrometinfo.www.shared.dto.ChoiceDTO_MapperImpl=UnnecessaryImport fr.agrometinfo.www.shared.dto.IndicatorDTOBeanJsonDeserializerImpl=UnnecessaryImport fr.agrometinfo.www.shared.dto.IndicatorDTOBeanJsonSerializerImpl=UnnecessaryImport fr.agrometinfo.www.shared.dto.IndicatorDTO_MapperImpl=UnnecessaryImport +fr.agrometinfo.www.shared.dto.MessageDTOBeanJsonSerializerImpl=UnnecessaryImport fr.agrometinfo.www.shared.dto.PeriodDTOBeanJsonDeserializerImpl=UnnecessaryImport fr.agrometinfo.www.shared.dto.PeriodDTOBeanJsonSerializerImpl=UnnecessaryImport fr.agrometinfo.www.shared.dto.PeriodDTO_MapperImpl=UnnecessaryImport @@ -14,6 +15,7 @@ fr.agrometinfo.www.shared.dto.SimpleFeatureBeanJsonSerializerImpl=UnnecessaryImp fr.agrometinfo.www.shared.dto.SummaryDTOBeanJsonDeserializerImpl=UnnecessaryImport fr.agrometinfo.www.shared.dto.SummaryDTOBeanJsonSerializerImpl=UnnecessaryImport fr.agrometinfo.www.shared.dto.SummaryDTO_MapperImpl=UnnecessaryImport +fr.agrometinfo.www.shared.service.ApplicationServiceFactory=UnnecessaryImport fr.agrometinfo.www.shared.service.IndicatorServiceFactory=UnnecessaryImport org.geojson.FeatureBeanJsonDeserializerImpl=UnnecessaryImport org.geojson.FeatureBeanJsonSerializerImpl=UnnecessaryImport diff --git a/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/MessageDTO.java b/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/MessageDTO.java new file mode 100644 index 0000000000000000000000000000000000000000..12253ed084ed9cd26ddd7056bf3f715d0d54a5dc --- /dev/null +++ b/www-shared/src/main/java/fr/agrometinfo/www/shared/dto/MessageDTO.java @@ -0,0 +1,65 @@ +package fr.agrometinfo.www.shared.dto; + +/** + * E-mail message from "Contact us" form. + * + * @author Olivier Maury + */ +public class MessageDTO { + /** + * User's e-mail address. + */ + private String emailAddress; + + /** + * Message. + */ + private String message; + + /** + * User's name. + */ + private String name; + + /** + * @return user's e-mail address + */ + public final String getEmailAddress() { + return emailAddress; + } + + /** + * @return the message + */ + public final String getMessage() { + return message; + } + + /** + * @return user's name + */ + public final String getName() { + return name; + } + + /** + * @param value user's e-mail address + */ + public final void setEmailAddress(final String value) { + this.emailAddress = value; + } + + /** + * @param value message + */ + public final void setMessage(final String value) { + this.message = value; + } + + /** + * @param value user's name + */ + public final void setName(final String value) { + this.name = value; + } +} diff --git a/www-shared/src/main/java/fr/agrometinfo/www/shared/service/ApplicationService.java b/www-shared/src/main/java/fr/agrometinfo/www/shared/service/ApplicationService.java new file mode 100644 index 0000000000000000000000000000000000000000..0082e1c85ca25870d3d858b7a9e13790e7f0b34b --- /dev/null +++ b/www-shared/src/main/java/fr/agrometinfo/www/shared/service/ApplicationService.java @@ -0,0 +1,38 @@ +package fr.agrometinfo.www.shared.service; + +import javax.ws.rs.POST; +import javax.ws.rs.Path; + +import org.dominokit.rest.shared.request.service.annotations.RequestBody; +import org.dominokit.rest.shared.request.service.annotations.RequestFactory; + +import fr.agrometinfo.www.shared.dto.MessageDTO; + +/** + * Interface for client and server resource. + * + * @author Olivier Maury + */ +@RequestFactory +@Path(ApplicationService.PATH) +public interface ApplicationService { + /** + * Service base path. + */ + String PATH = "application"; + + /** + * Path for {@link ApplicationService#send()}. + */ + String PATH_SEND_EMAIL = "send"; + + /** + * Send an e-mail message to AgroMetInfo team. + * + * @param message message + * @return nothing + */ + @POST + @Path(PATH_SEND_EMAIL) + String sendMessage(@RequestBody MessageDTO message); +}