DetailsActivity: Check for authentication here too.
[online-glom:gwt-glom.git] / src / main / java / org / glom / web / server / OnlineGlomImages.java
1 /*
2  * Copyright (C) 2012 Openismus GmbH
3  *
4  * This file is part of GWT-Glom.
5  *
6  * GWT-Glom is free software: you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as published by the
8  * Free Software Foundation, either version 3 of the License, or (at your
9  * option) any later version.
10  *
11  * GWT-Glom is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
14  * for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with GWT-Glom.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 package org.glom.web.server;
21
22 import java.io.ByteArrayInputStream;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.OutputStream;
26 import java.net.URLConnection;
27 import java.sql.Connection;
28 import java.sql.ResultSet;
29 import java.sql.SQLException;
30 import java.sql.Statement;
31 import java.util.ArrayList;
32 import java.util.List;
33
34 import javax.servlet.ServletException;
35 import javax.servlet.http.HttpServlet;
36 import javax.servlet.http.HttpServletRequest;
37 import javax.servlet.http.HttpServletResponse;
38
39 import org.apache.commons.lang3.StringUtils;
40 import org.glom.web.server.libglom.Document;
41 import org.glom.web.shared.TypedDataItem;
42 import org.glom.web.shared.libglom.Field;
43 import org.glom.web.shared.libglom.layout.LayoutItem;
44 import org.glom.web.shared.libglom.layout.LayoutItemField;
45 import org.glom.web.shared.libglom.layout.LayoutItemImage;
46
47 import com.google.gwt.http.client.Response;
48
49 /**
50  * @author Murray Cumming <murrayc@murrayc.com>
51  * 
52  */
53 public class OnlineGlomImages extends HttpServlet {
54
55         private static final long serialVersionUID = -3550760617357422853L;
56         
57         ConfiguredDocumentSet configuredDocumentSet = new ConfiguredDocumentSet();
58         
59         public OnlineGlomImages() {
60                 try {
61                         configuredDocumentSet.readConfiguration();
62                 } catch (ServletException e) {
63                         Log.error("Configuration error", e);
64                 }
65         }
66         
67         private void doError(HttpServletResponse resp, int errorCode, final String errorMessage) throws IOException {
68                 Log.error(errorMessage);
69                 resp.sendError(errorCode, errorMessage);
70         }
71         
72         private void doError(HttpServletResponse resp, int errorCode, final String errorMessage, final String documentID) throws IOException {
73                 Log.error(documentID, errorMessage);
74                 resp.sendError(errorCode, errorMessage);
75         }
76
77         @Override
78         public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
79
80                 //These match the history token keys in DetailsPlace:
81                 final String attrDocumentID = StringUtils.defaultString(req.getParameter("document"));
82                 final String attrTableName = StringUtils.defaultString(req.getParameter("table"));
83                 final String attrPrimaryKeyValue = StringUtils.defaultString(req.getParameter("value"));
84                 final String attrFieldName = StringUtils.defaultString(req.getParameter("field"));
85                 
86                 //To request a static LayoutItemImage from the document
87                 //instead of from the database:
88                 final String attrLayoutName = StringUtils.defaultString(req.getParameter("layout"));
89                 final String attrLayoutPath = StringUtils.defaultString(req.getParameter("layoutpath"));
90                 
91                 if(StringUtils.isEmpty(attrDocumentID)) {
92                         doError(resp, Response.SC_NOT_FOUND, "No document ID was specified.");
93                         return;
94                 }
95                 
96                 if(StringUtils.isEmpty(attrTableName)) {
97                         doError(resp, Response.SC_NOT_FOUND, "No table name was specified.", attrDocumentID);
98                         return;
99                 }
100                 
101                 //TODO: Is it from the database or is it a static LayouteItemText from the document.
102                 
103                 final boolean fromDb = !StringUtils.isEmpty(attrPrimaryKeyValue);
104                 final boolean fromLayout = !StringUtils.isEmpty(attrLayoutName);
105                 
106                 if(!fromDb && !fromLayout) {
107                         doError(resp, Response.SC_NOT_FOUND, "No primary key value or layout name was specified.", attrDocumentID);
108                         return;
109                 }
110                 
111                 if(fromDb && StringUtils.isEmpty(attrFieldName)) {
112                         doError(resp, Response.SC_NOT_FOUND, "No field name was specified.", attrDocumentID);
113                         return;
114                 }
115                 
116                 final ConfiguredDocument configuredDocument = configuredDocumentSet.getDocument(attrDocumentID);
117                 if(configuredDocument == null) {
118                         doError(resp, Response.SC_NOT_FOUND, "The specified document was not found.", attrDocumentID);
119                         return;
120                 }
121                 
122                 if(!configuredDocument.isAuthenticated()) {
123                         doError(resp, Response.SC_NOT_FOUND, "No access to the document.", attrDocumentID);
124                         return;
125                 }
126
127                 final Document document = configuredDocument.getDocument();
128                 if(document == null) {
129                         doError(resp, Response.SC_NOT_FOUND, "The specified document details were not found.", attrDocumentID);
130                         return;
131                 }
132                 
133                 byte[] bytes = null;
134                 if(fromDb) {
135                         bytes = getImageFromDatabase(resp, attrDocumentID, attrTableName, attrPrimaryKeyValue, attrFieldName,
136                                 configuredDocument, document);
137                 } else {
138                         bytes = getImageFromDocument(resp, attrDocumentID, attrTableName, attrLayoutName, attrLayoutPath,
139                                         configuredDocument, document);
140                 }
141                 
142                 if(bytes == null) {
143                         doError(resp, Response.SC_NOT_FOUND, "The image bytes could not be found. Please see the earlier error.", attrDocumentID);
144                         return;
145                 }
146                 
147                 final InputStream is = new ByteArrayInputStream(bytes);
148                 final String contentType = URLConnection.guessContentTypeFromStream(is);        
149                 resp.setContentType(contentType);
150
151                 // Set content size:
152                 resp.setContentLength((int) bytes.length);
153
154                 // Open the output stream:
155                 final OutputStream out = resp.getOutputStream();
156
157                 // Copy the contents to the output stream
158                 out.write(bytes);
159                 out.close();
160         }
161
162         /** Get the image from a specific <data_layout_text> node of a specific layout for a specific table in the document,
163          * with no access to the database data.
164          * 
165          * @param resp
166          * @param attrDocumentID
167          * @param attrTableName
168          * @param attrLayoutName
169          * @param attrLayoutPath
170          * @param configuredDocument
171          * @param document
172          * @return
173          * @throws IOException 
174          */
175         private byte[] getImageFromDocument(HttpServletResponse resp, final String attrDocumentID, final String attrTableName,
176                         final String attrLayoutName, final String attrLayoutPath, final ConfiguredDocument configuredDocument, final Document document) throws IOException {
177                 final LayoutItem item = document.getLayoutItemByPath(attrTableName, attrLayoutName, attrLayoutPath);
178                 
179                 if(item == null) {
180                         doError(resp, Response.SC_NOT_FOUND, "The item specifed by the layout path could not be found, attrLayoutPath=" + attrLayoutPath, attrDocumentID);
181                         return null;
182                 }
183                 
184                 if(!(item instanceof LayoutItemImage)) {
185                         doError(resp, Response.SC_NOT_FOUND, "The item specifed by the layout path is not an image. It has class: " + item.getClass().getName() + " and item name=" + item.getName() + ", attrLayoutPath=" + attrLayoutPath, attrDocumentID);
186                         return null;
187                 }
188                 
189                 final LayoutItemImage image = (LayoutItemImage)item;
190                 return image.getImage().getImageData();
191         }
192
193         /** Get the image from a specific field of a specific record in a specific table in the database.
194          * 
195          * @param resp
196          * @param attrDocumentID
197          * @param attrTableName
198          * @param attrPrimaryKeyValue
199          * @param attrFieldName
200          * @param configuredDocument
201          * @param document
202          * @return
203          * @throws IOException
204          */
205         private byte[] getImageFromDatabase(HttpServletResponse resp, final String attrDocumentID,
206                         final String attrTableName, final String attrPrimaryKeyValue, final String attrFieldName,
207                         final ConfiguredDocument configuredDocument, final Document document) throws IOException {
208                 final Field field = document.getField(attrTableName, attrFieldName);
209                 if(field == null) {
210                         doError(resp, Response.SC_NOT_FOUND, "The specified field was not found: field=" + attrFieldName, attrDocumentID);
211                         return null;
212                 }
213                 
214                 final Field fieldPrimaryKey = document.getTablePrimaryKeyField(attrTableName);
215                 
216                 TypedDataItem primaryKeyValue = new TypedDataItem();
217                 primaryKeyValue.setNumber(Double.parseDouble(attrPrimaryKeyValue));
218                 
219                 final LayoutItemField layoutItemField = new LayoutItemField();
220                 layoutItemField.setFullFieldDetails(field);
221                 final List<LayoutItemField> fieldsToGet = new ArrayList<LayoutItemField>();
222                 fieldsToGet.add(layoutItemField);
223                 final String query = SqlUtils.buildSqlSelectWithKey(attrTableName, fieldsToGet, fieldPrimaryKey, primaryKeyValue);
224                 
225                 Connection connection = null;
226                 try {
227                         connection = configuredDocument.getCpds().getConnection();
228                 } catch (final SQLException e) {
229                         //e.printStackTrace();
230
231                         doError(resp, Response.SC_INTERNAL_SERVER_ERROR, "SQL exception: " + e.getMessage(), attrDocumentID);
232                         return null;
233                 }
234                 
235                 Statement st = null;
236                 try {
237                         st = connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
238                 } catch (SQLException e) {
239                         doError(resp, Response.SC_INTERNAL_SERVER_ERROR, "SQL exception: " + e.getMessage(), attrDocumentID);
240                         return null;
241                 }
242                 
243                 if(st == null) {
244                         doError(resp, Response.SC_INTERNAL_SERVER_ERROR, "The SQL statement is null.", attrDocumentID);
245                         return null;
246                 }
247
248                 ResultSet rs = null;
249                 try {
250                         rs = st.executeQuery(query);
251                 } catch (SQLException e) {
252                         doError(resp, Response.SC_INTERNAL_SERVER_ERROR, "SQL exception: " + e.getMessage(), attrDocumentID);
253                         return null;
254                 }
255
256                 if(rs == null) {
257                         doError(resp, Response.SC_INTERNAL_SERVER_ERROR, "The SQL result set is null.", attrDocumentID);
258                         return null;
259                 }
260                 
261                 byte[] bytes = null;
262                 try {
263                         rs.next();
264                         bytes = rs.getBytes(1); //This is 1-indexed, not 0-indexed.
265                 } catch (SQLException e) {
266                         doError(resp, Response.SC_INTERNAL_SERVER_ERROR, "SQL exception: " + e.getMessage(), attrDocumentID);
267                         return null;
268                 }
269
270                 if(bytes == null) {
271                         doError(resp, Response.SC_INTERNAL_SERVER_ERROR, "The database contained null.", attrDocumentID);
272                         return null;
273                 }
274
275                 return bytes;
276         }
277 }