Log exception when a JSON reply can not be generated.
[Sone.git] / src / main / java / net / pterodactylus / sone / web / ajax / JsonPage.java
1 /*
2  * Sone - JsonPage.java - Copyright © 2010–2013 David Roden
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17
18 package net.pterodactylus.sone.web.ajax;
19
20 import java.io.ByteArrayOutputStream;
21 import java.io.IOException;
22 import java.io.OutputStreamWriter;
23 import java.io.PrintWriter;
24 import java.net.URI;
25 import java.util.logging.Level;
26 import java.util.logging.Logger;
27
28 import net.pterodactylus.sone.data.Sone;
29 import net.pterodactylus.sone.web.WebInterface;
30 import net.pterodactylus.sone.web.page.FreenetPage;
31 import net.pterodactylus.sone.web.page.FreenetRequest;
32 import net.pterodactylus.util.io.Closer;
33 import net.pterodactylus.util.json.JsonObject;
34 import net.pterodactylus.util.json.JsonUtils;
35 import net.pterodactylus.util.logging.Logging;
36 import net.pterodactylus.util.web.Page;
37 import net.pterodactylus.util.web.Response;
38 import freenet.clients.http.SessionManager.Session;
39 import freenet.clients.http.ToadletContext;
40
41 /**
42  * A JSON page is a specialized {@link Page} that will always return a JSON
43  * object to the browser, e.g. for use with AJAX or other scripting frameworks.
44  *
45  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
46  */
47 public abstract class JsonPage implements FreenetPage {
48
49         /** The logger. */
50         private static final Logger logger = Logging.getLogger(JsonPage.class);
51
52         /** The path of the page. */
53         private final String path;
54
55         /** The Sone web interface. */
56         protected final WebInterface webInterface;
57
58         /**
59          * Creates a new JSON page at the given path.
60          *
61          * @param path
62          *            The path of the page
63          * @param webInterface
64          *            The Sone web interface
65          */
66         public JsonPage(String path, WebInterface webInterface) {
67                 this.path = path;
68                 this.webInterface = webInterface;
69         }
70
71         //
72         // ACCESSORS
73         //
74
75         /**
76          * Returns the current session, creating a new session if there is no
77          * current session.
78          *
79          * @param toadletContenxt
80          *            The toadlet context
81          * @return The current session, or {@code null} if there is no current
82          *         session
83          */
84         protected Session getCurrentSession(ToadletContext toadletContenxt) {
85                 return webInterface.getCurrentSession(toadletContenxt);
86         }
87
88         /**
89          * Returns the current session, creating a new session if there is no
90          * current session and {@code create} is {@code true}.
91          *
92          * @param toadletContenxt
93          *            The toadlet context
94          * @param create
95          *            {@code true} to create a new session if there is no current
96          *            session, {@code false} otherwise
97          * @return The current session, or {@code null} if there is no current
98          *         session
99          */
100         protected Session getCurrentSession(ToadletContext toadletContenxt, boolean create) {
101                 return webInterface.getCurrentSession(toadletContenxt, create);
102         }
103
104         /**
105          * Returns the currently logged in Sone.
106          *
107          * @param toadletContext
108          *            The toadlet context
109          * @return The currently logged in Sone, or {@code null} if no Sone is
110          *         currently logged in
111          */
112         protected Sone getCurrentSone(ToadletContext toadletContext) {
113                 return webInterface.getCurrentSone(toadletContext);
114         }
115
116         /**
117          * Returns the currently logged in Sone.
118          *
119          * @param toadletContext
120          *            The toadlet context
121          * @param create
122          *            {@code true} to create a new session if no session exists,
123          *            {@code false} to not create a new session
124          * @return The currently logged in Sone, or {@code null} if no Sone is
125          *         currently logged in
126          */
127         protected Sone getCurrentSone(ToadletContext toadletContext, boolean create) {
128                 return webInterface.getCurrentSone(toadletContext, create);
129         }
130
131         //
132         // METHODS FOR SUBCLASSES TO OVERRIDE
133         //
134
135         /**
136          * This method is called to create the JSON object that is returned back to
137          * the browser.
138          *
139          * @param request
140          *            The request to handle
141          * @return The created JSON object
142          */
143         protected abstract JsonObject createJsonObject(FreenetRequest request);
144
145         /**
146          * Returns whether this command needs the form password for authentication
147          * and to prevent abuse.
148          *
149          * @return {@code true} if the form password (given as “formPassword”) is
150          *         required, {@code false} otherwise
151          */
152         @SuppressWarnings("static-method")
153         protected boolean needsFormPassword() {
154                 return true;
155         }
156
157         /**
158          * Returns whether this page requires the user to be logged in.
159          *
160          * @return {@code true} if the user needs to be logged in to use this page,
161          *         {@code false} otherwise
162          */
163         @SuppressWarnings("static-method")
164         protected boolean requiresLogin() {
165                 return true;
166         }
167
168         //
169         // PROTECTED METHODS
170         //
171
172         /**
173          * Creates a success reply.
174          *
175          * @return A reply signaling success
176          */
177         protected static JsonObject createSuccessJsonObject() {
178                 return new JsonObject().put("success", true);
179         }
180
181         /**
182          * Creates an error reply.
183          *
184          * @param error
185          *            The error that has occured
186          * @return The JSON object, signalling failure and the error code
187          */
188         protected static JsonObject createErrorJsonObject(String error) {
189                 return new JsonObject().put("success", false).put("error", error);
190         }
191
192         //
193         // PAGE METHODS
194         //
195
196         /**
197          * {@inheritDoc}
198          */
199         @Override
200         public String getPath() {
201                 return path;
202         }
203
204         /**
205          * {@inheritDoc}
206          */
207         @Override
208         public boolean isPrefixPage() {
209                 return false;
210         }
211
212         /**
213          * {@inheritDoc}
214          */
215         @Override
216         public Response handleRequest(FreenetRequest request, Response response) throws IOException {
217                 if (webInterface.getCore().getPreferences().isRequireFullAccess() && !request.getToadletContext().isAllowedFullAccess()) {
218                         return response.setStatusCode(403).setStatusText("Forbidden").setContentType("application/json").write(JsonUtils.format(new JsonObject().put("success", false).put("error", "auth-required")));
219                 }
220                 if (needsFormPassword()) {
221                         String formPassword = request.getHttpRequest().getParam("formPassword");
222                         if (!webInterface.getFormPassword().equals(formPassword)) {
223                                 return response.setStatusCode(403).setStatusText("Forbidden").setContentType("application/json").write(JsonUtils.format(new JsonObject().put("success", false).put("error", "auth-required")));
224                         }
225                 }
226                 if (requiresLogin()) {
227                         if (getCurrentSone(request.getToadletContext(), false) == null) {
228                                 return response.setStatusCode(403).setStatusText("Forbidden").setContentType("application/json").write(JsonUtils.format(new JsonObject().put("success", false).put("error", "auth-required")));
229                         }
230                 }
231                 try {
232                         JsonObject jsonObject = createJsonObject(request);
233                         return response.setStatusCode(200).setStatusText("OK").setContentType("application/json").write(JsonUtils.format(jsonObject));
234                 } catch (Exception e1) {
235                         logger.log(Level.WARNING, "Error executing JSON page!", e1);
236                         return response.setStatusCode(500).setStatusText(e1.getMessage()).setContentType("text/plain").write(dumpStackTrace(e1));
237                 }
238         }
239
240         /**
241          * {@inheritDoc}
242          */
243         @Override
244         public boolean isLinkExcepted(URI link) {
245                 return false;
246         }
247
248         //
249         // PRIVATE METHODS
250         //
251
252         /**
253          * Returns a byte array containing the stack trace of the given throwable.
254          *
255          * @param t
256          *            The throwable whose stack trace to dump into an array
257          * @return The array with the stack trace, or an empty array if the stack
258          *         trace could not be dumped
259          */
260         private static byte[] dumpStackTrace(Throwable t) {
261                 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
262                 OutputStreamWriter writer = null;
263                 PrintWriter printWriter = null;
264                 try {
265                         writer = new OutputStreamWriter(byteArrayOutputStream, "uTF-8");
266                         printWriter = new PrintWriter(writer);
267                         t.printStackTrace(printWriter);
268                         byteArrayOutputStream.flush();
269                         return byteArrayOutputStream.toByteArray();
270                 } catch (IOException ioe1) {
271                         /* quite not possible. */
272                         return new byte[0];
273                 } finally {
274                         Closer.close(printWriter);
275                         Closer.close(writer);
276                         Closer.close(byteArrayOutputStream);
277                 }
278         }
279
280 }