internationalized user error messages
[mir.git] / source / Mir.java
1 /*
2  * Copyright (C) 2001, 2002 The Mir-coders group
3  *
4  * This file is part of Mir.
5  *
6  * Mir is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * Mir is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Mir; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  * In addition, as a special exception, The Mir-coders gives permission to link
21  * the code of this program with the com.oreilly.servlet library, any library
22  * licensed under the Apache Software License, The Sun (tm) Java Advanced
23  * Imaging library (JAI), The Sun JIMI library (or with modified versions of
24  * the above that use the same license as the above), and distribute linked
25  * combinations including the two.  You must obey the GNU General Public
26  * License in all respects for all of the code used other than the above
27  * mentioned libraries.  If you modify this file, you may extend this exception
28  * to your version of the file, but you are not obligated to do so.  If you do
29  * not wish to do so, delete this exception statement from your version.
30  */
31
32 import java.io.IOException;
33 import java.io.PrintWriter;
34 import java.lang.reflect.Method;
35 import java.util.GregorianCalendar;
36 import java.util.HashMap;
37 import java.util.Iterator;
38 import java.util.List;
39 import java.util.Locale;
40 import java.util.Map;
41 import java.util.Vector;
42
43 import javax.servlet.ServletException;
44 import javax.servlet.UnavailableException;
45 import javax.servlet.http.HttpServletRequest;
46 import javax.servlet.http.HttpServletResponse;
47 import javax.servlet.http.HttpSession;
48
49 import org.apache.struts.util.MessageResources;
50
51 import freemarker.template.SimpleHash;
52 import freemarker.template.SimpleList;
53 import freemarker.template.SimpleScalar;
54 import freemarker.template.TemplateModel;
55
56 import mir.config.MirPropertiesConfiguration;
57 import mir.generator.FreemarkerGenerator;
58 import mir.log.LoggerWrapper;
59 import mir.misc.HTMLTemplateProcessor;
60 import mir.misc.StringUtil;
61 import mir.servlet.AbstractServlet;
62 import mir.servlet.ServletModule;
63 import mir.servlet.ServletModuleDispatch;
64 import mir.servlet.ServletModuleExc;
65 import mir.servlet.ServletModuleUserExc;
66 import mir.util.ExceptionFunctions;
67 import mir.util.StringRoutines;
68 import mircoders.entity.EntityUsers;
69 import mircoders.global.MirGlobal;
70 import mircoders.module.ModuleMessage;
71 import mircoders.module.ModuleUsers;
72 import mircoders.storage.DatabaseArticleType;
73 import mircoders.storage.DatabaseMessages;
74 import mircoders.storage.DatabaseUsers;
75
76
77
78
79 /**
80  * Mir.java - main servlet, that dispatches to servletmodules
81  *
82  * @author $Author: zapata $
83  * @version $Id: Mir.java,v 1.37 2003/03/09 05:52:12 zapata Exp $
84  *
85  */
86 public class Mir extends AbstractServlet {
87   private static ModuleUsers usersModule = null;
88   private static ModuleMessage messageModule = null;
89   private final static Map servletModuleInstanceHash = new HashMap();
90
91   //I don't know about making this static cause it removes the
92   //possibility to change the config on the fly.. -mh
93   private static List loginLanguages = null;
94   public HttpSession session;
95
96   public void doGet(HttpServletRequest aRequest, HttpServletResponse aResponse)
97     throws ServletException, IOException {
98     doPost(aRequest, aResponse);
99   }
100
101   protected TemplateModel getLoginLanguages() throws ServletException {
102     synchronized (Mir.class) {
103       try {
104         if (loginLanguages == null) {
105           MessageResources messageResources2 =
106             MessageResources.getMessageResources("bundles.admin");
107           MessageResources messageResources =
108             MessageResources.getMessageResources("bundles.adminlocal");
109           List languages =
110             StringRoutines.splitString(MirGlobal.getConfigPropertyWithDefault(
111                 "Mir.Login.Languages", "en"), ";");
112
113           loginLanguages = new Vector();
114
115           Iterator i = languages.iterator();
116
117           while (i.hasNext()) {
118             String code = (String) i.next();
119             Locale locale = new Locale(code, "");
120             String name = messageResources.getMessage(locale, "languagename");
121
122             if (name == null) {
123               name = messageResources2.getMessage(locale, "languagename");
124             }
125
126             if (name == null) {
127               name = code;
128             }
129
130             Map record = new HashMap();
131             record.put("name", name);
132             record.put("code", code);
133             loginLanguages.add(record);
134           }
135         }
136
137         return FreemarkerGenerator.makeAdapter(loginLanguages);
138       }
139       catch (Throwable t) {
140         throw new ServletException(t.getMessage());
141       }
142     }
143   }
144
145   // FIXME: this should probalby go into AbstractServlet so it can be used in
146   // OpenMir as well -mh
147   protected String getDefaultLanguage(HttpServletRequest aRequest) {
148     String defaultlanguage =
149       MirGlobal.getConfigPropertyWithDefault("Mir.Login.DefaultLanguage", "");
150
151     if (defaultlanguage.length() == 0) {
152       Locale locale = aRequest.getLocale();
153       defaultlanguage = locale.getLanguage();
154     }
155
156     return defaultlanguage;
157   }
158
159   public void doPost(HttpServletRequest aRequest, HttpServletResponse aResponse)
160     throws ServletException, IOException, UnavailableException {
161     long startTime = System.currentTimeMillis();
162     long sessionConnectTime = 0;
163     EntityUsers userEntity;
164     String http = "";
165
166     if ((configuration.getString("RootUri") == null) ||
167         configuration.getString("RootUri").equals("")) {
168       configuration.setProperty("RootUri", aRequest.getContextPath());
169     }
170
171     configuration.addProperty("ServletName", getServletName());
172
173     //*** test
174     // Log.info(this, "blalalala");
175     session = aRequest.getSession(true);
176     userEntity = (EntityUsers) session.getAttribute("login.uid");
177
178     if (aRequest.getServerPort() == 443) {
179       http = "https";
180     } else {
181       http = "http";
182     }
183
184     //make sure client browsers don't cache anything
185     setNoCaching(aResponse);
186
187     //FIXME: this seems kind of hackish and only here because we can have
188     // default other than the one that the browser is set to.
189     Locale locale = new Locale(getDefaultLanguage(aRequest), "");
190     MessageResources messageResources =
191       MessageResources.getMessageResources("bundles.admin");
192     String htmlcharset = messageResources.getMessage(locale, "htmlcharset");
193
194     aResponse.setContentType("text/html; charset=" + htmlcharset);
195
196     String moduleName = aRequest.getParameter("module");
197     checkLanguage(session, aRequest);
198
199     /** @todo for cleanup and readability this should be moved to
200      *  method loginIfNecessary() */
201     if ((moduleName != null) && moduleName.equals("direct")) {
202       //...
203     }
204
205     // Authentication
206     if (((moduleName != null) && moduleName.equals("login")) ||
207         (userEntity == null)) {
208       String user = aRequest.getParameter("login");
209       String passwd = aRequest.getParameter("password");
210       logger.debug("--login: evaluating for user: " + user);
211       userEntity = allowedUser(user, passwd);
212
213       if (userEntity == null) {
214         // login failed: redirecting to login
215         logger.warn("--login: failed!");
216         _sendLoginPage(aResponse, aRequest, aResponse.getWriter());
217
218         return;
219       } else if ((moduleName != null) && moduleName.equals("login")) {
220         // login successful
221         logger.info("--login: successful! setting uid: " + userEntity.getId());
222         session.setAttribute("login.uid", userEntity);
223         logger.debug("--login: trying to retrieve login.target");
224
225         String target = (String) session.getAttribute("login.target");
226
227         if (target != null) {
228           logger.debug("Redirect: " + target);
229
230           int serverPort = aRequest.getServerPort();
231           String redirect = "";
232           String redirectString = "";
233
234           if (serverPort == 80) {
235             redirect =
236               aResponse.encodeURL(http + "://" + aRequest.getServerName() + target);
237             redirectString =
238               "<html><head><meta http-equiv=refresh content=\"1;URL=" +
239               redirect + "\"></head><body>going <a href=\"" + redirect +
240               "\">Mir</a></body></html>";
241           } else {
242             redirect =
243               aResponse.encodeURL(http + "://" + aRequest.getServerName() + ":" +
244                 aRequest.getServerPort() + target);
245             redirectString =
246               "<html><head><meta http-equiv=refresh content=\"1;URL=" +
247               redirect + "\"></head><body>going <a href=\"" + redirect +
248               "\">Mir</a></body></html>";
249           }
250
251           aResponse.getWriter().println(redirectString);
252
253           //aResponse.sendRedirect(redirect);
254         } else {
255           // redirecting to default target
256           logger.debug("--login: no target - redirecting to default");
257           _sendStartPage(aResponse, aRequest, aResponse.getWriter(), userEntity);
258         }
259
260         return;
261       }
262        // if login succesful
263     }
264      // if login
265
266     if ((moduleName != null) && moduleName.equals("logout")) {
267       logger.info("--logout");
268       session.invalidate();
269
270       //session = aRequest.getSession(true);
271       //checkLanguage(session, aRequest);
272       _sendLoginPage(aResponse, aRequest, aResponse.getWriter());
273
274       return;
275     }
276
277     // Check if authed!
278     if (userEntity == null) {
279       // redirect to loginpage
280       String redirectString = aRequest.getRequestURI();
281       String queryString = aRequest.getQueryString();
282
283       if ((queryString != null) && !queryString.equals("")) {
284         redirectString += ("?" + aRequest.getQueryString());
285         logger.debug("STORING: " + redirectString);
286         session.setAttribute("login.target", redirectString);
287       }
288
289       _sendLoginPage(aResponse, aRequest, aResponse.getWriter());
290
291       return;
292     }
293
294     // If no module is specified goto standard startpage
295     if ((moduleName == null) || moduleName.equals("")) {
296       logger.debug("no module: redirect to standardpage");
297       _sendStartPage(aResponse, aRequest, aResponse.getWriter(), userEntity);
298
299       return;
300     }
301
302     // end of auth
303     // From now on regular dispatching...
304     try {
305       // get servletmodule by parameter and continue with dispacher
306       ServletModule smod = getServletModuleForName(moduleName);
307       ServletModuleDispatch.dispatch(smod, aRequest, aResponse);
308     }
309     catch (Throwable e) {
310       Throwable cause = ExceptionFunctions.traceCauseException(e);
311
312       if (cause instanceof ServletModuleUserExc)
313         handleUserError(aRequest, aResponse, aResponse.getWriter(), (ServletModuleUserExc) cause);
314       else
315         handleError(aRequest, aResponse, aResponse.getWriter(), cause);
316
317     }
318
319     // timing...
320     sessionConnectTime = System.currentTimeMillis() - startTime;
321     logger.info("EXECTIME (" + moduleName + "): " + sessionConnectTime + " ms");
322   }
323
324   /**
325    *  Private method getServletModuleForName returns ServletModule
326    *  from Cache
327    *
328    * @param moduleName
329    * @return ServletModule
330    *
331    */
332   private static ServletModule getServletModuleForName(String moduleName) throws ServletModuleExc {
333     // Instance in Map ?
334     if (!servletModuleInstanceHash.containsKey(moduleName)) {
335       // was not found in hash...
336       try {
337         Class theServletModuleClass = null;
338
339         try {
340           // first we try to get ServletModule from stern.che3.servlet
341           theServletModuleClass =
342             Class.forName("mircoders.servlet.ServletModule" + moduleName);
343         } catch (ClassNotFoundException e) {
344           // on failure, we try to get it from lib-layer
345           theServletModuleClass =
346             Class.forName("mir.servlet.ServletModule" + moduleName);
347         }
348
349         Method m = theServletModuleClass.getMethod("getInstance", null);
350         ServletModule smod = (ServletModule) m.invoke(null, null);
351
352         // we put it into map for further reference
353         servletModuleInstanceHash.put(moduleName, smod);
354
355         return smod;
356       }
357       catch (Exception e) {
358         throw new ServletModuleExc("*** error resolving classname for " + moduleName + " -- " + e.getMessage());
359       }
360     }
361     else {
362       return (ServletModule) servletModuleInstanceHash.get(moduleName);
363     }
364   }
365
366   private void handleUserError(HttpServletRequest aRequest, HttpServletResponse aResponse,
367                                PrintWriter out, ServletModuleUserExc anException) {
368     try {
369       logger.info("user error: " + anException.getMessage());
370       SimpleHash modelRoot = new SimpleHash();
371       MessageResources messages = MessageResources.getMessageResources("bundles.admin");
372       modelRoot.put("errorstring",
373           new SimpleScalar(
374               messages.getMessage(getLocale(aRequest), anException.getMessage(), anException.getParameters())
375           ));
376       modelRoot.put("date", new SimpleScalar(StringUtil.date2readableDateTime(new GregorianCalendar())));
377       HTMLTemplateProcessor.process(
378           aResponse,MirPropertiesConfiguration.instance().getString("Mir.UserErrorTemplate"),
379           modelRoot, out, getLocale(aRequest));
380       out.close();
381     }
382     catch (Exception e) {
383       logger.error("Error in UserErrorTemplate");
384     }
385
386   }
387
388   private void handleError(HttpServletRequest aRequest, HttpServletResponse aResponse,PrintWriter out, Throwable anException) {
389
390     try {
391       logger.error("error: " + anException);
392       SimpleHash modelRoot = new SimpleHash();
393       modelRoot.put("errorstring", new SimpleScalar(anException.getMessage()));
394       modelRoot.put("date", new SimpleScalar(StringUtil.date2readableDateTime(
395                                                new GregorianCalendar())));
396       HTMLTemplateProcessor.process(aResponse,MirPropertiesConfiguration.instance().getString("Mir.ErrorTemplate"),
397                                     modelRoot,out, getLocale(aRequest));
398       out.close();
399     }
400     catch (Exception e) {
401       logger.error("Error in ErrorTemplate");
402     }
403   }
404
405   /**
406    *  evaluate login for user / password
407    */
408   protected EntityUsers allowedUser(String user, String password) {
409     try {
410       if (usersModule == null) {
411         usersModule = new ModuleUsers(DatabaseUsers.getInstance());
412       }
413
414       return usersModule.getUserForLogin(user, password);
415     }
416     catch (Exception e) {
417       logger.debug(e.getMessage());
418       e.printStackTrace(logger.asPrintWriter(logger.DEBUG_MESSAGE));
419
420       return null;
421     }
422   }
423
424   // Redirect-methods
425   private void _sendLoginPage(HttpServletResponse aResponse, HttpServletRequest aRequest,
426     PrintWriter out) {
427     String loginTemplate = configuration.getString("Mir.LoginTemplate");
428     String sessionUrl = aResponse.encodeURL("");
429
430     try {
431       SimpleHash mergeData = new SimpleHash();
432       SimpleList languages = new SimpleList();
433
434       mergeData.put("session", sessionUrl);
435
436       mergeData.put("defaultlanguage", getDefaultLanguage(aRequest));
437       mergeData.put("languages", getLoginLanguages());
438
439       HTMLTemplateProcessor.process(aResponse, loginTemplate, mergeData, out,
440         getLocale(aRequest));
441     }
442     catch (Throwable e) {
443       handleError(aRequest, aResponse, out, e);
444     }
445   }
446
447   private void _sendStartPage(HttpServletResponse aResponse, HttpServletRequest aRequest,
448     PrintWriter out, EntityUsers userEntity) {
449     String startTemplate = "templates/admin/start_admin.template";
450     String sessionUrl = aResponse.encodeURL("");
451
452     try {
453       // merge with logged in user and messages
454       SimpleHash mergeData = new SimpleHash();
455       mergeData.put("session", sessionUrl);
456       mergeData.put("login_user", userEntity);
457
458       if (messageModule == null) {
459         messageModule = new ModuleMessage(DatabaseMessages.getInstance());
460       }
461
462       mergeData.put("messages",
463         messageModule.getByWhereClause(null, "webdb_create desc", 0, 10));
464
465       mergeData.put("articletypes",
466         DatabaseArticleType.getInstance().selectByWhereClause("", "id", 0, 20));
467
468       HTMLTemplateProcessor.process(aResponse, startTemplate, mergeData, out,
469         getLocale(aRequest));
470     }
471     catch (Exception e) {
472       e.printStackTrace(logger.asPrintWriter(LoggerWrapper.DEBUG_MESSAGE));
473       handleError(aRequest, aResponse, out, e);
474     }
475   }
476
477   public String getServletInfo() {
478     return "Mir " + configuration.getString("Mir.Version");
479   }
480
481   private void checkLanguage(HttpSession session, HttpServletRequest aRequest) {
482     // a lang parameter always sets the language
483     String lang = aRequest.getParameter("language");
484
485     if (lang != null) {
486       logger.info("selected language " + lang + " overrides accept-language");
487       setLanguage(session, lang);
488       setLocale(session, new Locale(lang, ""));
489     }
490     // otherwise store language from accept header in session
491     else if (session.getAttribute("Language") == null) {
492       logger.info("accept-language is " + aRequest.getLocale().getLanguage());
493       setLanguage(session, aRequest.getLocale().getLanguage());
494       setLocale(session, aRequest.getLocale());
495     }
496   }
497 }