001/*
002 * JGrapes Event Driven Framework
003 * Copyright (C) 2022, 2023 Michael N. Lipp
004 * 
005 * This program is free software; you can redistribute it and/or modify it 
006 * under the terms of the GNU Affero General Public License as published by 
007 * the Free Software Foundation; either version 3 of the License, or 
008 * (at your option) any later version.
009 * 
010 * This program is distributed in the hope that it will be useful, but 
011 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
012 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public 
013 * License for more details.
014 * 
015 * You should have received a copy of the GNU Affero General Public License 
016 * along with this program; if not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jgrapes.mail;
020
021import java.util.List;
022import java.util.Map;
023import java.util.Optional;
024import java.util.Properties;
025import org.jgrapes.core.Channel;
026import org.jgrapes.core.Event;
027import org.jgrapes.core.annotation.Handler;
028import org.jgrapes.io.util.ConnectionManager;
029import org.jgrapes.util.Password;
030import org.jgrapes.util.events.ConfigurationUpdate;
031
032/**
033 * Provides a base class for mail components using connections.
034 *
035 * @param <O> the type of the open event
036 * @param <C> the type of the channel
037 */
038public abstract class MailConnectionManager<
039        C extends MailConnectionManager<C, O>.AbstractMailChannel,
040        O extends Event<?>> extends ConnectionManager<C> {
041
042    protected final Properties mailProps = new Properties();
043    private Password password;
044
045    /**
046     * Creates a new server using the given channel.
047     * 
048     * @param componentChannel the component's channel
049     */
050    public MailConnectionManager(Channel componentChannel) {
051        super(componentChannel);
052    }
053
054    /**
055     * Sets the mail properties. See 
056     * [the Jakarta Mail](https://jakarta.ee/specifications/mail/2.0/apidocs/jakarta.mail/jakarta/mail/package-summary.html)
057     * documentation for available settings. The given properties are
058     * merged with the already existing properties.
059     *
060     * @param props the props
061     * @return the mail monitor
062     */
063    public MailConnectionManager<C, O>
064            setMailProperties(Map<String, String> props) {
065        mailProps.putAll(props);
066        return this;
067    }
068
069    /**
070     * Sets the password.
071     *
072     * @param password the new password
073     */
074    public MailConnectionManager<C, O> setPassword(Password password) {
075        this.password = password;
076        return this;
077    }
078
079    /**
080     * Return the password.
081     *
082     * @return the optional password
083     */
084    protected Optional<Password> password() {
085        return Optional.ofNullable(password);
086    }
087
088    /**
089     * Configure the component. Attempts to access all paths specified
090     * in the package description in sequence as described in 
091     * {@link org.jgrapes.mail}. For each path, merges the
092     * `mail` properties and invokes {@link #configureComponent}
093     * with the available key/value pairs.  
094     *  
095     * @param event the event
096     */
097    @Handler
098    @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
099    public void onConfigUpdate(ConfigurationUpdate event) {
100        for (var path : List.of("/org.jgrapes.mail/Component",
101            "/org.jgrapes.mail/" + getClass().getSimpleName(),
102            "/org_jgrapes_mail/Component",
103            "/org_jgrapes_mail/" + getClass().getSimpleName(),
104            componentPath())) {
105            event.values(path + "/mail").ifPresent(c -> {
106                for (var e : c.entrySet()) {
107                    mailProps.put("mail." + e.getKey(), e.getValue());
108                }
109            });
110            event.values(path).ifPresent(v -> {
111                Optional.ofNullable(v.get("password"))
112                    .ifPresent(p -> setPassword(new Password(p.toCharArray())));
113                configureComponent(v);
114            });
115        }
116    }
117
118    /**
119     * Configure the component specific values.
120     *
121     * @param values the values
122     */
123    protected abstract void configureComponent(Map<String, String> values);
124
125    /**
126     * A sub-channel for mail connections.
127     */
128    protected abstract class AbstractMailChannel
129            extends ConnectionManager<C>.Connection implements MailChannel {
130
131        private final O openEvent;
132
133        /**
134         * Instantiates a new mail channel instance.
135         *
136         * @param event the main channel
137         */
138        public AbstractMailChannel(O event, Channel channel) {
139            super(channel);
140            openEvent = event;
141        }
142
143        /**
144         * Returns the event that caused this connection to be opened.
145         * 
146         * @return the event
147         */
148        public O openEvent() {
149            return openEvent;
150        }
151
152    }
153}