001    /****************************************************************
002     * Licensed to the Apache Software Foundation (ASF) under one   *
003     * or more contributor license agreements.  See the NOTICE file *
004     * distributed with this work for additional information        *
005     * regarding copyright ownership.  The ASF licenses this file   *
006     * to you under the Apache License, Version 2.0 (the            *
007     * "License"); you may not use this file except in compliance   *
008     * with the License.  You may obtain a copy of the License at   *
009     *                                                              *
010     *   http://www.apache.org/licenses/LICENSE-2.0                 *
011     *                                                              *
012     * Unless required by applicable law or agreed to in writing,   *
013     * software distributed under the License is distributed on an  *
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
015     * KIND, either express or implied.  See the License for the    *
016     * specific language governing permissions and limitations      *
017     * under the License.                                           *
018     ****************************************************************/
019    
020    package org.apache.james.protocols.smtp.core;
021    
022    import java.util.ArrayList;
023    import java.util.List;
024    
025    import org.apache.james.protocols.api.Request;
026    import org.apache.james.protocols.api.Response;
027    import org.apache.james.protocols.api.handler.CommandHandler;
028    import org.apache.james.protocols.api.handler.ExtensibleHandler;
029    import org.apache.james.protocols.smtp.SMTPResponse;
030    import org.apache.james.protocols.smtp.SMTPRetCode;
031    import org.apache.james.protocols.smtp.SMTPSession;
032    import org.apache.james.protocols.smtp.hook.HookResult;
033    import org.apache.james.protocols.smtp.hook.HookResultHook;
034    import org.apache.james.protocols.smtp.hook.HookReturnCode;
035    
036    /**
037     * Abstract class which Handle hook-aware CommanHandler.
038     * 
039     */
040    public abstract class AbstractHookableCmdHandler<Hook extends org.apache.james.protocols.smtp.hook.Hook> implements CommandHandler<SMTPSession>, ExtensibleHandler {
041    
042    
043        private List<Hook> hooks;
044        private List<HookResultHook> rHooks;
045    
046        /**
047         * Handle command processing
048         * 
049         * @see org.apache.james.protocols.api.handler.CommandHandler
050         * #onCommand(org.apache.james.protocols.api.ProtocolSession, Request)
051         */
052        public Response onCommand(SMTPSession session, Request request) {
053            String command = request.getCommand();
054            String parameters = request.getArgument();
055            SMTPResponse response = doFilterChecks(session, command, parameters);
056    
057            if (response == null) {
058    
059                response = processHooks(session, command, parameters);
060                if (response == null) {
061                    return doCoreCmd(session, command, parameters);
062                } else {
063                    return response;
064                }
065            } else {
066                return response;
067            }
068    
069        }
070    
071        /**
072         * Process all hooks for the given command
073         * 
074         * @param session
075         *            the SMTPSession object
076         * @param command
077         *            the command
078         * @param parameters
079         *            the paramaters
080         * @return SMTPResponse
081         */
082        private SMTPResponse processHooks(SMTPSession session, String command,
083                String parameters) {
084            List<Hook> hooks = getHooks();
085            if (hooks != null) {
086                int count = hooks.size();
087                for (int i = 0; i < count; i++) {
088                    Hook rawHook = hooks.get(i);
089                    session.getLogger().debug("executing hook " + rawHook.getClass().getName());
090                    long start = System.currentTimeMillis();
091                    
092                    HookResult hRes = callHook(rawHook, session, parameters);
093                    long executionTime = System.currentTimeMillis() - start;
094    
095                    if (rHooks != null) {
096                        for (int i2 = 0; i2 < rHooks.size(); i2++) {
097                            Object rHook = rHooks.get(i2);
098                            session.getLogger().debug("executing hook " + rHook);
099                            hRes = ((HookResultHook) rHook).onHookResult(session, hRes, executionTime, rawHook);
100                        }
101                    }
102                    
103                    // call the core cmd if we receive a ok return code of the hook so no other hooks are executed
104                    if ((hRes.getResult() & HookReturnCode.OK) == HookReturnCode.OK) {
105                        SMTPResponse response = doCoreCmd(session, command, parameters);
106                        if ((hRes.getResult() & HookReturnCode.DISCONNECT) == HookReturnCode.DISCONNECT) {
107                            response.setEndSession(true);
108                        }
109                          return response;
110                    } else {
111                            SMTPResponse res = calcDefaultSMTPResponse(hRes);
112                            if (res != null) {
113                                    return res;
114                            }
115                    }
116                }
117            }
118            return null;
119        }
120    
121        /**
122         * Must be implemented by hookable cmd handlers to make the effective call to an hook.
123         * 
124         * @param rawHook the hook
125         * @param session the session
126         * @param parameters the parameters
127         * @return the HookResult, will be calculated using HookResultToSMTPResponse.
128         */
129        protected abstract HookResult callHook(Hook rawHook, SMTPSession session, String parameters);
130    
131        /**
132         * Convert the HookResult to SMTPResponse using default values. Should be override for using own values
133         * 
134         * @param result HookResult
135         * @return SMTPResponse
136         */
137        public static SMTPResponse calcDefaultSMTPResponse(HookResult result) {
138            if (result != null) {
139                int rCode = result.getResult();
140                String smtpRetCode = result.getSmtpRetCode();
141                String smtpDesc = result.getSmtpDescription();
142        
143                if ((rCode &HookReturnCode.DENY) == HookReturnCode.DENY) {
144                    if (smtpRetCode == null)
145                        smtpRetCode = SMTPRetCode.TRANSACTION_FAILED;
146                    if (smtpDesc == null)
147                        smtpDesc = "Email rejected";
148        
149                    SMTPResponse response =  new SMTPResponse(smtpRetCode, smtpDesc);
150                    if ((rCode & HookReturnCode.DISCONNECT) == HookReturnCode.DISCONNECT) {
151                        response.setEndSession(true);
152                    }
153                    return response;
154                } else if (rCode == HookReturnCode.DENYSOFT) {
155                    if (smtpRetCode == null)
156                        smtpRetCode = SMTPRetCode.LOCAL_ERROR;
157                    if (smtpDesc == null)
158                        smtpDesc = "Temporary problem. Please try again later";
159        
160                    SMTPResponse response = new SMTPResponse(smtpRetCode, smtpDesc);
161                    if ((rCode & HookReturnCode.DISCONNECT) == HookReturnCode.DISCONNECT) {
162                        response.setEndSession(true);
163                    }
164                    return response;
165                } else if ((rCode & HookReturnCode.OK) == HookReturnCode.OK) {
166                    if (smtpRetCode == null)
167                        smtpRetCode = SMTPRetCode.MAIL_OK;
168                    if (smtpDesc == null)
169                        smtpDesc = "Command accepted";
170        
171                    SMTPResponse response = new SMTPResponse(smtpRetCode, smtpDesc);
172                    if ((rCode & HookReturnCode.DISCONNECT) == HookReturnCode.DISCONNECT) {
173                        response.setEndSession(true);
174                    }
175                    return response;
176                } else if ((rCode & HookReturnCode.DISCONNECT) == HookReturnCode.DISCONNECT) {
177                    SMTPResponse response = new SMTPResponse("");
178                    response.setEndSession(true);
179                    return response;
180                } else {
181                    // Return null as default
182                    return null;
183                }
184            } else {
185                return null;
186            }
187        }
188    
189        /**
190         * Execute Syntax checks and return a SMTPResponse if a syntax error was
191         * detected, otherwise null.
192         * 
193         * @param session
194         * @param command
195         * @param parameters
196         * @return smtp response if a syntax error was detected, otherwise <code>null</code>
197         */
198        protected abstract SMTPResponse doFilterChecks(SMTPSession session,
199                String command, String parameters);
200    
201        /**
202         * Execute the core commandHandling.
203         * 
204         * @param session
205         * @param command
206         * @param parameters
207         * @return smtp response
208         */
209        protected abstract SMTPResponse doCoreCmd(SMTPSession session,
210                String command, String parameters);
211        
212    
213        /**
214         * @see org.apache.james.protocols.api.handler.ExtensibleHandler#getMarkerInterfaces()
215         */
216        public List<Class<?>> getMarkerInterfaces() {
217            List<Class<?>> classes = new ArrayList<Class<?>>(2);
218            classes.add(getHookInterface());
219            classes.add(HookResultHook.class);
220            return classes;
221        }
222    
223        /**
224         * Return the interface which hooks need to implement to hook in
225         * 
226         * @return interface
227         */
228        protected abstract Class<Hook> getHookInterface();
229    
230        /**
231         * @see org.apache.james.protocols.api.handler.ExtensibleHandler#wireExtensions(java.lang.Class,
232         *      java.util.List)
233         */
234        @SuppressWarnings("unchecked")
235        public void wireExtensions(Class interfaceName, List extension) {
236            if (getHookInterface().equals(interfaceName)) {
237                this.hooks = extension;
238            } else if (HookResultHook.class.equals(interfaceName)) {
239                this.rHooks = extension;
240            }
241    
242        }
243    
244        /**
245         * Return a list which holds all hooks for the cmdHandler
246         * 
247         * @return list containing all hooks for the cmd handler
248         */
249        protected List<Hook> getHooks() {
250            return hooks;
251        }
252    
253    }