View Javadoc

1   /**
2    * This file Copyright (c) 2013-2014 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.ui.admincentral;
35  
36  import info.magnolia.cms.util.ServletUtil;
37  
38  import java.io.BufferedWriter;
39  import java.io.IOException;
40  import java.io.OutputStreamWriter;
41  import java.io.PrintWriter;
42  import java.util.List;
43  
44  import javax.inject.Inject;
45  import javax.servlet.ServletException;
46  import javax.servlet.ServletOutputStream;
47  import javax.servlet.http.HttpServletRequest;
48  import javax.servlet.http.HttpServletResponse;
49  
50  import org.slf4j.Logger;
51  import org.slf4j.LoggerFactory;
52  
53  import com.vaadin.server.BootstrapFragmentResponse;
54  import com.vaadin.server.BootstrapListener;
55  import com.vaadin.server.BootstrapPageResponse;
56  import com.vaadin.server.DeploymentConfiguration;
57  import com.vaadin.server.RequestHandler;
58  import com.vaadin.server.ServiceException;
59  import com.vaadin.server.SessionInitEvent;
60  import com.vaadin.server.SessionInitListener;
61  import com.vaadin.server.UIProvider;
62  import com.vaadin.server.VaadinServlet;
63  import com.vaadin.server.VaadinServletRequest;
64  import com.vaadin.server.VaadinServletResponse;
65  import com.vaadin.server.VaadinServletService;
66  import com.vaadin.server.communication.ServletBootstrapHandler;
67  import com.vaadin.shared.ApplicationConstants;
68  
69  /**
70   * The AdmincentralVaadinServlet.
71   */
72  public class AdmincentralVaadinServlet extends VaadinServlet {
73  
74      private static final Logger log = LoggerFactory.getLogger(AdmincentralVaadinServlet.class);
75  
76      private static final String ERROR_PAGE_STYLE = "<style>a {color: inherit; text-decoration:none;}" +
77              "html, body {height:100%; margin:0;}" +
78              ".error-message {color:#fff; font-family: Verdana, sans-serif; padding:24px; line-height:1.3; overflow-x:hidden; overflow-y:auto;}" +
79              "h2 {font-size:5em; font-family:DINWebPro, sans-serif; font-weight: normal; margin:0;}" +
80              ".v-button-link {font-size: 2em;} .v-button-link .v-button-caption {text-decoration:none;}" +
81              "#stacktrace {font-family: monospace; display:none; color:#3e5900;}" +
82              ".viewerror {color:#aabf2f;} .v-button-link:hover, .v-button-link:focus {color:#93bac6;}</style>";
83  
84      /**
85       * URL param forcing restart of vaadin application.
86       */
87      public static final String RESTART_APPLICATION_PARAM = "?restartApplication";
88  
89      private UIProvider admincentralUiProvider;
90  
91      @Inject
92      public AdmincentralVaadinServlet(UIProvider admincentralUiProvider) {
93          this.admincentralUiProvider = admincentralUiProvider;
94      }
95  
96      @Override
97      protected void servletInitialized() throws ServletException {
98          super.servletInitialized();
99          getService().addSessionInitListener(new SessionInitListener() {
100             @Override
101             public void sessionInit(SessionInitEvent event) {
102                 event.getSession().addBootstrapListener(new BootstrapListener() {
103 
104                     @Override
105                     public void modifyBootstrapPage(BootstrapPageResponse response) {
106                         response.getDocument().head().append("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\" />");
107                     }
108 
109                     @Override
110                     public void modifyBootstrapFragment(BootstrapFragmentResponse response) {
111                     }
112                 });
113 
114                 // Set up and configure UIProvider for the admincentral
115                 if (admincentralUiProvider != null) {
116                     event.getSession().addUIProvider(admincentralUiProvider);
117                 } else {
118                     log.error("Could not inject AdmincentralUIProvider.");
119                 }
120             }
121         });
122     }
123 
124     @Override
125     protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
126         try {
127             String requestURI = ServletUtil.stripPathParameters(request.getRequestURI());
128             if (requestURI != null && requestURI.endsWith("undefined.cache.js")) {
129                 writeUnsupportedBrowserPage(request, response);
130             } else {
131                 super.service(request, response);
132             }
133         } catch (Exception e) {
134             log.error("An internal error has occurred in the VaadinServlet.", e);
135             writeServerErrorPage(request, response, e);
136         }
137     }
138 
139     @Override
140     protected void criticalNotification(VaadinServletRequest request, VaadinServletResponse response, String caption, String message, String details, String url) throws IOException {
141         // invoking critical notifications only for UIDL requests, otherwise we let it fall back to writing the error page.
142         if (isUidlRequest(request)) {
143             super.criticalNotification(request, response, caption, message, details, url);
144         }
145     }
146 
147     private void writeServerErrorPage(HttpServletRequest request, HttpServletResponse response, Exception e) throws IOException {
148         if (!isUidlRequest(request)) {
149             // Create an HTML response with the error
150 
151             // compute restart application URL at previous location
152             String url = request.getRequestURL().toString() + RESTART_APPLICATION_PARAM;
153             String fragment = request.getParameter("v-loc");
154             if (fragment != null && fragment.indexOf("#") != -1) {
155                 url += fragment.substring(fragment.indexOf("#"));
156             }
157 
158             StringBuilder output = new StringBuilder();
159 
160             output.append(ERROR_PAGE_STYLE);
161             output.append("<div class=\"v-magnolia-shell\" style=\"height:100%;\">" +
162                     "<div id=\"main-launcher\"><a href=\"" + url + "\"><img id=\"logo\" src=\"./../VAADIN/themes/admincentraltheme/img/logo-magnolia.svg\" /></a></div>" +
163                     "<div class=\"error-message v-shell-viewport-slot\">");
164 
165             output.append("<h2>Whoops!</h2>");
166             output.append("<p>The server has encountered an internal error.</p>");
167 
168             output.append("<div class=\"v-button v-widget link v-button-link\" tabindex=\"0\" role=\"button\">" +
169                     "<a href=\"" + url + "\">[<span class=\"v-button-caption\">Click here to attempt to recover from this</span>]</a></div>");
170 
171             output.append("<p>We apologize for any inconvenience caused.</p>");
172 
173             output.append("<p>If you keep experiencing difficulties, please contact your system administrator.<br/>" +
174                     "Make sure you send along the stack trace below.</p>");
175 
176             output.append("<div class=\"v-button v-widget link v-button-link viewerror\" tabindex=\"0\" role=\"button\" onclick=\"var st=document.getElementById('stacktrace');st.style.display=(st.style.display=='block')?'none':'block';\">" +
177                     "[<span class=\"v-button-caption\">Click here to show the error's stack trace</span>]</div>");
178             output.append(getStackTrace(e));
179 
180             output.append("</div></div>");
181 
182             // prepend document head if this is the first vaadin request
183             output.insert(0, "<html><head><meta charset=\"UTF-8\"/><meta content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\" name=\"viewport\"/><title>Magnolia 5</title><link rel=\"stylesheet\" type=\"text/css\" href=\"./../VAADIN/themes/admincentral/styles.css\"/></head><body>");
184             output.append("</body></html>");
185 
186             // make sure vaadin writes this output in the document rather than treat it as a UIDL response
187             response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
188             writeResponse(response, "text/html; charset=UTF-8", output.toString());
189         }
190     }
191 
192     private void writeUnsupportedBrowserPage(HttpServletRequest request, HttpServletResponse response) throws IOException {
193         // build message
194         StringBuilder output = new StringBuilder();
195         output.append(ERROR_PAGE_STYLE);
196         output.append("<div class=\"v-magnolia-shell\" style=\"height:100%;\">" +
197                 "<div id=\"main-launcher\"><a href=\"#\"><img id=\"logo\" src=\"./../VAADIN/themes/admincentraltheme/img/logo-magnolia.svg\"></a></div>" +
198                 "<div class=\"error-message v-shell-viewport-slot\">" +
199                 "<h2>Sorry.</h2>" +
200                 "<p>You're trying to use Magnolia 5 on a browser we currently do not support.</p>" +
201                 "<p>Please log in using either Firefox, Chrome, Safari or IE8+.<br />" +
202                 "We apologize for any inconvenience caused.</p>");
203         output.append("</div></div>");
204 
205         // wrap as JS response
206         output.replace(0, output.length(), output.toString().replaceAll("\\\"", "\\\\\\\""));
207         output.insert(0, "document.body.innerHTML = \"");
208         output.append("\";");
209 
210         writeResponse(response, "text/javascript; charset=UTF-8", output.toString());
211     }
212 
213     private String getStackTrace(Throwable e) {
214         final StringBuilder result = new StringBuilder("<p id=\"stacktrace\">");
215         result.append(e.toString());
216 
217         // add each element of the stack trace
218         for (StackTraceElement element : e.getStackTrace()) {
219             result.append("<br/>");
220             result.append("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;at ");
221             result.append(element);
222         }
223         result.append("</p>");
224         return result.toString();
225     }
226 
227     /**
228      * same as {@link com.vaadin.server.ServletPortletHelper#isUIDLRequest}.
229      */
230     private boolean isUidlRequest(HttpServletRequest request) {
231         String prefix = ApplicationConstants.UIDL_PATH + '/';
232         String pathInfo = request.getPathInfo();
233 
234         if (pathInfo == null) {
235             return false;
236         }
237 
238         if (!prefix.startsWith("/")) {
239             prefix = '/' + prefix;
240         }
241 
242         if (pathInfo.startsWith(prefix)) {
243             return true;
244         }
245 
246         return false;
247     }
248 
249     /**
250      * same as {@link VaadinServlet#writeResponse}.
251      */
252     private void writeResponse(HttpServletResponse response,
253             String contentType, String output) throws IOException {
254         response.setContentType(contentType);
255         final ServletOutputStream out = response.getOutputStream();
256         // Set the response type
257         final PrintWriter outWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(out, "UTF-8")));
258         outWriter.print(output);
259         outWriter.flush();
260         outWriter.close();
261         out.flush();
262     }
263 
264     @Override
265     protected VaadinServletService createServletService(DeploymentConfiguration deploymentConfiguration) throws ServiceException {
266         VaadinServletService service = new VaadinServletService(this, deploymentConfiguration) {
267 
268             @Override
269             protected List<RequestHandler> createRequestHandlers() throws ServiceException {
270                 List<RequestHandler> handlers = super.createRequestHandlers();
271                 for (int i = 0; i < handlers.size(); i++) {
272                     RequestHandler handler = handlers.get(i);
273                     if (handler instanceof ServletBootstrapHandler) {
274                         handlers.set(i, new ServletBootstrapHandler() {
275 
276                             @Override
277                             protected String getServiceUrl(BootstrapContext context) {
278 
279                                 // We replace the default ServletBootstrapHandler with our own so that we can specify
280                                 // the serviceUrl explicitly. It's otherwise left empty making the client determine it
281                                 // using location.href. The client does not play well with this and appends paths after
282                                 // the JSESSIONID instead of in front of it. This results in a problem loading resources
283                                 // specified using @JavaScript and @StyleSheet.
284                                 //
285                                 // see com.vaadin.client.ApplicationConfiguration.loadFromDOM()
286                                 // see MGNLUI-2291
287                                 // see http://dev.vaadin.com/ticket/10974
288 
289                                 return ServletUtil.getOriginalRequestURI(((VaadinServletRequest)context.getRequest()).getHttpServletRequest());
290                             }
291                         });
292                         break;
293                     }
294                 }
295                 return handlers;
296             }
297         };
298         service.init();
299         return service;
300     }
301 }