View Javadoc
1   /*
2    * Licensed under the GPL License. You may not use this file except in compliance with the License.
3    * You may obtain a copy of the License at
4    *
5    *   https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
6    *
7    * THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
8    * WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
9    * PURPOSE.
10   */
11  package psiprobe.controllers.sql;
12  
13  import java.sql.Connection;
14  import java.sql.PreparedStatement;
15  import java.sql.ResultSet;
16  import java.sql.ResultSetMetaData;
17  import java.sql.SQLException;
18  import java.util.ArrayList;
19  import java.util.LinkedHashMap;
20  import java.util.List;
21  import java.util.Map;
22  
23  import javax.naming.NamingException;
24  import javax.servlet.http.HttpServletRequest;
25  import javax.servlet.http.HttpServletResponse;
26  import javax.servlet.http.HttpSession;
27  import javax.sql.DataSource;
28  
29  import org.apache.catalina.Context;
30  import org.slf4j.Logger;
31  import org.slf4j.LoggerFactory;
32  import org.springframework.beans.factory.annotation.Value;
33  import org.springframework.stereotype.Controller;
34  import org.springframework.web.bind.ServletRequestUtils;
35  import org.springframework.web.bind.annotation.RequestMapping;
36  import org.springframework.web.servlet.ModelAndView;
37  import org.springframework.web.util.HtmlUtils;
38  
39  import psiprobe.controllers.AbstractContextHandlerController;
40  import psiprobe.model.sql.DataSourceTestInfo;
41  
42  /**
43   * Executes an SQL query through a given datasource to test database connectivity. Displays results
44   * returned by the query.
45   */
46  @Controller
47  public class ExecuteSqlController extends AbstractContextHandlerController {
48  
49    /** The Constant logger. */
50    private static final Logger logger = LoggerFactory.getLogger(ExecuteSqlController.class);
51  
52    @RequestMapping(path = "/sql/recordset.ajax")
53    @Override
54    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
55        throws Exception {
56      return super.handleRequest(request, response);
57    }
58  
59    @Override
60    protected ModelAndView handleContext(String contextName, Context context,
61        HttpServletRequest request, HttpServletResponse response) throws Exception {
62  
63      String resourceName = ServletRequestUtils.getStringParameter(request, "resource");
64      String sql = ServletRequestUtils.getStringParameter(request, "sql");
65  
66      if (sql == null || sql.isEmpty() || sql.trim().isEmpty()) {
67        request.setAttribute("errorMessage",
68            getMessageSourceAccessor().getMessage("probe.src.dataSourceTest.sql.required"));
69  
70        return new ModelAndView(getViewName());
71      }
72  
73      int maxRows = ServletRequestUtils.getIntParameter(request, "maxRows", 0);
74      int rowsPerPage = ServletRequestUtils.getIntParameter(request, "rowsPerPage", 0);
75      int historySize = ServletRequestUtils.getIntParameter(request, "historySize", 0);
76  
77      // store current option values and query history in a session attribute
78  
79      HttpSession sess = request.getSession(false);
80      DataSourceTestInfo sessData =
81          (DataSourceTestInfo) sess.getAttribute(DataSourceTestInfo.DS_TEST_SESS_ATTR);
82  
83      synchronized (sess) {
84        if (sessData == null) {
85          sessData = new DataSourceTestInfo();
86          sess.setAttribute(DataSourceTestInfo.DS_TEST_SESS_ATTR, sessData);
87        }
88  
89        sessData.setMaxRows(maxRows);
90        sessData.setRowsPerPage(rowsPerPage);
91        sessData.setHistorySize(historySize);
92        sessData.addQueryToHistory(sql);
93      }
94  
95      DataSource dataSource = null;
96  
97      try {
98        dataSource = getContainerWrapper().getResourceResolver().lookupDataSource(context,
99            resourceName, getContainerWrapper());
100     } catch (NamingException e) {
101       request.setAttribute("errorMessage", getMessageSourceAccessor().getMessage(
102           "probe.src.dataSourceTest.resource.lookup.failure", new Object[] {resourceName}));
103       logger.trace("", e);
104     }
105 
106     if (dataSource == null) {
107       request.setAttribute("errorMessage", getMessageSourceAccessor().getMessage(
108           "probe.src.dataSourceTest.resource.lookup.failure", new Object[] {resourceName}));
109     } else {
110       List<Map<String, String>> results = null;
111       int rowsAffected = 0;
112 
113       try {
114         // TODO: use Spring's jdbc template?
115         try (Connection conn = dataSource.getConnection()) {
116           conn.setAutoCommit(true);
117 
118           try (PreparedStatement stmt = conn.prepareStatement(sql)) {
119             boolean hasResultSet = stmt.execute();
120 
121             if (!hasResultSet) {
122               rowsAffected = stmt.getUpdateCount();
123             } else {
124               results = new ArrayList<>();
125 
126               try (ResultSet rs = stmt.getResultSet()) {
127                 ResultSetMetaData metaData = rs.getMetaData();
128 
129                 while (rs.next() && (maxRows < 0 || results.size() < maxRows)) {
130                   Map<String, String> record = new LinkedHashMap<>();
131 
132                   for (int i = 1; i <= metaData.getColumnCount(); i++) {
133                     String value = rs.getString(i);
134 
135                     if (rs.wasNull()) {
136                       value = getMessageSourceAccessor()
137                           .getMessage("probe.src.dataSourceTest.sql.null");
138                     } else {
139                       value = HtmlUtils.htmlEscape(value);
140                     }
141 
142                     // Pad the keys of columns with existing labels so they are distinct
143                     StringBuilder key = new StringBuilder(metaData.getColumnLabel(i));
144                     while (record.containsKey(key.toString())) {
145                       key.append(" ");
146                     }
147                     record.put(HtmlUtils.htmlEscape(key.toString()), value);
148                   }
149 
150                   results.add(record);
151                 }
152               }
153 
154               rowsAffected = results.size();
155             }
156           }
157         }
158 
159         // store the query results in the session attribute in order
160         // to support a result set pagination feature without re-executing the query
161 
162         synchronized (sess) {
163           sessData.setResults(results);
164         }
165 
166         ModelAndView mv = new ModelAndView(getViewName(), "results", results);
167         mv.addObject("rowsAffected", String.valueOf(rowsAffected));
168         mv.addObject("rowsPerPage", String.valueOf(rowsPerPage));
169 
170         return mv;
171       } catch (SQLException e) {
172         String message = getMessageSourceAccessor()
173             .getMessage("probe.src.dataSourceTest.sql.failure", new Object[] {e.getMessage()});
174         logger.error(message, e);
175         request.setAttribute("errorMessage", message);
176       }
177     }
178 
179     return new ModelAndView(getViewName());
180   }
181 
182   @Override
183   protected boolean isContextOptional() {
184     return true;
185   }
186 
187   @Value("ajax/sql/recordset")
188   @Override
189   public void setViewName(String viewName) {
190     super.setViewName(viewName);
191   }
192 
193 }