Bug fixed reported by Zapata that config.docRoot is null if first calling OpenMir...
[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 mir.config.MirPropertiesConfiguration;
50 import mir.generator.FreemarkerGenerator;
51 import mir.log.LoggerWrapper;
52 import mir.misc.HTMLTemplateProcessor;
53 import mir.misc.StringUtil;
54 import mir.servlet.AbstractServlet;
55 import mir.servlet.ServletModule;
56 import mir.servlet.ServletModuleDispatch;
57 import mir.servlet.ServletModuleExc;
58 import mir.servlet.ServletModuleUserExc;
59 import mir.util.ExceptionFunctions;
60 import mir.util.StringRoutines;
61 import mircoders.entity.EntityUsers;
62 import mircoders.global.MirGlobal;
63 import mircoders.module.ModuleMessage;
64 import mircoders.module.ModuleUsers;
65 import mircoders.storage.DatabaseArticleType;
66 import mircoders.storage.DatabaseMessages;
67 import mircoders.storage.DatabaseUsers;
68
69 import org.apache.struts.util.MessageResources;
70
71 import freemarker.template.SimpleHash;
72 import freemarker.template.SimpleList;
73 import freemarker.template.SimpleScalar;
74 import freemarker.template.TemplateModel;
75
76
77
78
79 /**
80  * Mir.java - main servlet, that dispatches to servletmodules
81  *
82  * @author $Author: idfx $
83  * @version $Id: Mir.java,v 1.39 2003/03/15 02:07:35 idfx 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   protected TemplateModel getLoginLanguages() throws ServletException {
97     synchronized (Mir.class) {
98       try {
99         if (loginLanguages == null) {
100           MessageResources messageResources2 =
101             MessageResources.getMessageResources("bundles.admin");
102           MessageResources messageResources =
103             MessageResources.getMessageResources("bundles.adminlocal");
104           List languages =
105             StringRoutines.splitString(MirGlobal.getConfigPropertyWithDefault(
106                 "Mir.Login.Languages", "en"), ";");
107
108           loginLanguages = new Vector();
109
110           Iterator i = languages.iterator();
111
112           while (i.hasNext()) {
113             String code = (String) i.next();
114             Locale locale = new Locale(code, "");
115             String name = messageResources.getMessage(locale, "languagename");
116
117             if (name == null) {
118               name = messageResources2.getMessage(locale, "languagename");
119             }
120
121             if (name == null) {
122               name = code;
123             }
124
125             Map record = new HashMap();
126             record.put("name", name);
127             record.put("code", code);
128             loginLanguages.add(record);
129           }
130         }
131
132         return FreemarkerGenerator.makeAdapter(loginLanguages);
133       }
134       catch (Throwable t) {
135         throw new ServletException(t.getMessage());
136       }
137     }
138   }
139
140   // FIXME: this should probalby go into AbstractServlet so it can be used in
141   // OpenMir as well -mh
142   protected String getDefaultLanguage(HttpServletRequest aRequest) {
143     String defaultlanguage =
144       MirGlobal.getConfigPropertyWithDefault("Mir.Login.DefaultLanguage", "");
145
146     if (defaultlanguage.length() == 0) {
147       Locale locale = aRequest.getLocale();
148       defaultlanguage = locale.getLanguage();
149     }
150
151     return defaultlanguage;
152   }
153
154   public void process(HttpServletRequest aRequest, HttpServletResponse aResponse)
155     throws ServletException, IOException, UnavailableException {
156     long startTime = System.currentTimeMillis();
157     long sessionConnectTime = 0;
158     EntityUsers userEntity;
159     String http = "";
160
161     configuration.addProperty("ServletName", getServletName());
162
163     session = aRequest.getSession(true);
164     userEntity = (EntityUsers) session.getAttribute("login.uid");
165
166     if (aRequest.getServerPort() == 443) {
167       http = "https";
168     } else {
169       http = "http";
170     }
171
172     //make sure client browsers don't cache anything
173     setNoCaching(aResponse);
174
175     //FIXME: this seems kind of hackish and only here because we can have
176     // default other than the one that the browser is set to.
177     Locale locale = new Locale(getDefaultLanguage(aRequest), "");
178     MessageResources messageResources =
179       MessageResources.getMessageResources("bundles.admin");
180     String htmlcharset = messageResources.getMessage(locale, "htmlcharset");
181
182     aResponse.setContentType("text/html; charset=" + htmlcharset);
183
184     String moduleName = aRequest.getParameter("module");
185     checkLanguage(session, aRequest);
186
187     /** @todo for cleanup and readability this should be moved to
188      *  method loginIfNecessary() */
189     if ((moduleName != null) && moduleName.equals("direct")) {
190       //...
191     }
192
193     // Authentication
194     if (((moduleName != null) && moduleName.equals("login")) ||
195         (userEntity == null)) {
196       String user = aRequest.getParameter("login");
197       String passwd = aRequest.getParameter("password");
198       logger.debug("--login: evaluating for user: " + user);
199       userEntity = allowedUser(user, passwd);
200
201       if (userEntity == null) {
202         // login failed: redirecting to login
203         logger.warn("--login: failed!");
204         _sendLoginPage(aResponse, aRequest, aResponse.getWriter());
205
206         return;
207       } else if ((moduleName != null) && moduleName.equals("login")) {
208         // login successful
209         logger.info("--login: successful! setting uid: " + userEntity.getId());
210         session.setAttribute("login.uid", userEntity);
211         logger.debug("--login: trying to retrieve login.target");
212
213         String target = (String) session.getAttribute("login.target");
214
215         if (target != null) {
216           logger.debug("Redirect: " + target);
217
218           int serverPort = aRequest.getServerPort();
219           String redirect = "";
220           String redirectString = "";
221
222           if (serverPort == 80) {
223             redirect =
224               aResponse.encodeURL(http + "://" + aRequest.getServerName() + target);
225             redirectString =
226               "<html><head><meta http-equiv=refresh content=\"1;URL=" +
227               redirect + "\"></head><body>going <a href=\"" + redirect +
228               "\">Mir</a></body></html>";
229           } else {
230             redirect =
231               aResponse.encodeURL(http + "://" + aRequest.getServerName() + ":" +
232                 aRequest.getServerPort() + target);
233             redirectString =
234               "<html><head><meta http-equiv=refresh content=\"1;URL=" +
235               redirect + "\"></head><body>going <a href=\"" + redirect +
236               "\">Mir</a></body></html>";
237           }
238
239           aResponse.getWriter().println(redirectString);
240
241           //aResponse.sendRedirect(redirect);
242         } else {
243           // redirecting to default target
244           logger.debug("--login: no target - redirecting to default");
245           _sendStartPage(aResponse, aRequest, aResponse.getWriter(), userEntity);
246         }
247
248         return;
249       }
250        // if login succesful
251     }
252      // if login
253
254     if ((moduleName != null) && moduleName.equals("logout")) {
255       logger.info("--logout");
256       session.invalidate();
257
258       //session = aRequest.getSession(true);
259       //checkLanguage(session, aRequest);
260       _sendLoginPage(aResponse, aRequest, aResponse.getWriter());
261
262       return;
263     }
264
265     // Check if authed!
266     if (userEntity == null) {
267       // redirect to loginpage
268       String redirectString = aRequest.getRequestURI();
269       String queryString = aRequest.getQueryString();
270
271       if ((queryString != null) && !queryString.equals("")) {
272         redirectString += ("?" + aRequest.getQueryString());
273         logger.debug("STORING: " + redirectString);
274         session.setAttribute("login.target", redirectString);
275       }
276
277       _sendLoginPage(aResponse, aRequest, aResponse.getWriter());
278
279       return;
280     }
281
282     // If no module is specified goto standard startpage
283     if ((moduleName == null) || moduleName.equals("")) {
284       logger.debug("no module: redirect to standardpage");
285       _sendStartPage(aResponse, aRequest, aResponse.getWriter(), userEntity);
286
287       return;
288     }
289
290     // end of auth
291     // From now on regular dispatching...
292     try {
293       // get servletmodule by parameter and continue with dispacher
294       ServletModule smod = getServletModuleForName(moduleName);
295       ServletModuleDispatch.dispatch(smod, aRequest, aResponse);
296     }
297     catch (Throwable e) {
298       Throwable cause = ExceptionFunctions.traceCauseException(e);
299
300       if (cause instanceof ServletModuleUserExc)
301         handleUserError(aRequest, aResponse, aResponse.getWriter(), (ServletModuleUserExc) cause);
302       else
303         handleError(aRequest, aResponse, aResponse.getWriter(), cause);
304
305     }
306
307     // timing...
308     sessionConnectTime = System.currentTimeMillis() - startTime;
309     logger.info("EXECTIME (" + moduleName + "): " + sessionConnectTime + " ms");
310   }
311
312   /**
313    *  Private method getServletModuleForName returns ServletModule
314    *  from Cache
315    *
316    * @param moduleName
317    * @return ServletModule
318    *
319    */
320   private static ServletModule getServletModuleForName(String moduleName) throws ServletModuleExc {
321     // Instance in Map ?
322     if (!servletModuleInstanceHash.containsKey(moduleName)) {
323       // was not found in hash...
324       try {
325         Class theServletModuleClass = null;
326
327         try {
328           // first we try to get ServletModule from stern.che3.servlet
329           theServletModuleClass =
330             Class.forName("mircoders.servlet.ServletModule" + moduleName);
331         } catch (ClassNotFoundException e) {
332           // on failure, we try to get it from lib-layer
333           theServletModuleClass =
334             Class.forName("mir.servlet.ServletModule" + moduleName);
335         }
336
337         Method m = theServletModuleClass.getMethod("getInstance", null);
338         ServletModule smod = (ServletModule) m.invoke(null, null);
339
340         // we put it into map for further reference
341         servletModuleInstanceHash.put(moduleName, smod);
342
343         return smod;
344       }
345       catch (Exception e) {
346         throw new ServletModuleExc("*** error resolving classname for " + moduleName + " -- " + e.getMessage());
347       }
348     }
349     else {
350       return (ServletModule) servletModuleInstanceHash.get(moduleName);
351     }
352   }
353
354   private void handleUserError(HttpServletRequest aRequest, HttpServletResponse aResponse,
355                                PrintWriter out, ServletModuleUserExc anException) {
356     try {
357       logger.info("user error: " + anException.getMessage());
358       SimpleHash modelRoot = new SimpleHash();
359       MessageResources messages = MessageResources.getMessageResources("bundles.admin");
360       modelRoot.put("errorstring",
361           new SimpleScalar(
362               messages.getMessage(getLocale(aRequest), anException.getMessage(), anException.getParameters())
363           ));
364       modelRoot.put("date", new SimpleScalar(StringUtil.date2readableDateTime(new GregorianCalendar())));
365       HTMLTemplateProcessor.process(
366           aResponse,MirPropertiesConfiguration.instance().getString("Mir.UserErrorTemplate"),
367           modelRoot, out, getLocale(aRequest));
368       out.close();
369     }
370     catch (Exception e) {
371       logger.error("Error in UserErrorTemplate");
372     }
373
374   }
375
376   private void handleError(HttpServletRequest aRequest, HttpServletResponse aResponse,PrintWriter out, Throwable anException) {
377
378     try {
379       logger.error("error: " + anException);
380       SimpleHash modelRoot = new SimpleHash();
381       modelRoot.put("errorstring", new SimpleScalar(anException.getMessage()));
382       modelRoot.put("date", new SimpleScalar(StringUtil.date2readableDateTime(
383                                                new GregorianCalendar())));
384       HTMLTemplateProcessor.process(aResponse,MirPropertiesConfiguration.instance().getString("Mir.ErrorTemplate"),
385                                     modelRoot,out, getLocale(aRequest));
386       out.close();
387     }
388     catch (Exception e) {
389       logger.error("Error in ErrorTemplate");
390     }
391   }
392
393   /**
394    *  evaluate login for user / password
395    */
396   protected EntityUsers allowedUser(String user, String password) {
397     try {
398       if (usersModule == null) {
399         usersModule = new ModuleUsers(DatabaseUsers.getInstance());
400       }
401
402       return usersModule.getUserForLogin(user, password);
403     }
404     catch (Exception e) {
405       logger.debug(e.getMessage());
406       e.printStackTrace(logger.asPrintWriter(LoggerWrapper.DEBUG_MESSAGE));
407
408       return null;
409     }
410   }
411
412   // Redirect-methods
413   private void _sendLoginPage(HttpServletResponse aResponse, HttpServletRequest aRequest,
414     PrintWriter out) {
415     String loginTemplate = configuration.getString("Mir.LoginTemplate");
416     String sessionUrl = aResponse.encodeURL("");
417
418     try {
419       SimpleHash mergeData = new SimpleHash();
420       SimpleList languages = new SimpleList();
421
422       mergeData.put("session", sessionUrl);
423
424       mergeData.put("defaultlanguage", getDefaultLanguage(aRequest));
425       mergeData.put("languages", getLoginLanguages());
426
427       HTMLTemplateProcessor.process(aResponse, loginTemplate, mergeData, out,
428         getLocale(aRequest));
429     }
430     catch (Throwable e) {
431       handleError(aRequest, aResponse, out, e);
432     }
433   }
434
435   private void _sendStartPage(HttpServletResponse aResponse, HttpServletRequest aRequest,
436     PrintWriter out, EntityUsers userEntity) {
437     String startTemplate = "templates/admin/start_admin.template";
438     String sessionUrl = aResponse.encodeURL("");
439
440     try {
441       // merge with logged in user and messages
442       SimpleHash mergeData = new SimpleHash();
443       mergeData.put("session", sessionUrl);
444       mergeData.put("login_user", userEntity);
445
446       if (messageModule == null) {
447         messageModule = new ModuleMessage(DatabaseMessages.getInstance());
448       }
449
450       mergeData.put("messages",
451         messageModule.getByWhereClause(null, "webdb_create desc", 0, 10));
452
453       mergeData.put("articletypes",
454         DatabaseArticleType.getInstance().selectByWhereClause("", "id", 0, 20));
455
456       HTMLTemplateProcessor.process(aResponse, startTemplate, mergeData, out,
457         getLocale(aRequest));
458     }
459     catch (Exception e) {
460       e.printStackTrace(logger.asPrintWriter(LoggerWrapper.DEBUG_MESSAGE));
461       handleError(aRequest, aResponse, out, e);
462     }
463   }
464
465   public String getServletInfo() {
466     return "Mir " + configuration.getString("Mir.Version");
467   }
468
469   private void checkLanguage(HttpSession session, HttpServletRequest aRequest) {
470     // a lang parameter always sets the language
471     String lang = aRequest.getParameter("language");
472
473     if (lang != null) {
474       logger.info("selected language " + lang + " overrides accept-language");
475       setLanguage(session, lang);
476       setLocale(session, new Locale(lang, ""));
477     }
478     // otherwise store language from accept header in session
479     else if (session.getAttribute("Language") == null) {
480       logger.info("accept-language is " + aRequest.getLocale().getLanguage());
481       setLanguage(session, aRequest.getLocale().getLanguage());
482       setLocale(session, aRequest.getLocale());
483     }
484   }
485 }