001/*
002 *  Copyright 2017 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.plugins.datasourcesexplorer;
017
018import java.sql.Connection;
019import java.sql.DatabaseMetaData;
020import java.sql.PreparedStatement;
021import java.sql.ResultSet;
022import java.sql.ResultSetMetaData;
023import java.sql.SQLException;
024import java.util.ArrayList;
025import java.util.HashMap;
026import java.util.HashSet;
027import java.util.List;
028import java.util.Map;
029import java.util.Set;
030
031import org.apache.avalon.framework.parameters.Parameters;
032import org.apache.avalon.framework.service.ServiceException;
033import org.apache.avalon.framework.service.ServiceManager;
034import org.apache.cocoon.ProcessingException;
035import org.apache.cocoon.acting.ServiceableAction;
036import org.apache.cocoon.environment.ObjectModelHelper;
037import org.apache.cocoon.environment.Redirector;
038import org.apache.cocoon.environment.Request;
039import org.apache.cocoon.environment.SourceResolver;
040import org.apache.commons.lang3.StringUtils;
041
042import org.ametys.core.cocoon.JSonReader;
043import org.ametys.core.datasource.ConnectionHelper;
044import org.ametys.core.datasource.SQLDataSourceManager;
045import org.ametys.core.datasource.dbtype.SQLDatabaseTypeExtensionPoint;
046import org.ametys.core.util.JSONUtils;
047
048/**
049 * Generates the sql data 
050 */
051public class GetSQLData extends ServiceableAction
052{
053    private JSONUtils _jsonUtils;
054    private SQLDatabaseTypeExtensionPoint _sqlDatabaseTypeExtensionPoint;
055    private SQLDataSourceManager _sqlDataSourceManager;
056
057    @Override
058    public void service(ServiceManager smanager) throws ServiceException
059    {
060        super.service(smanager);
061        _jsonUtils = (JSONUtils) smanager.lookup(JSONUtils.ROLE);
062        _sqlDatabaseTypeExtensionPoint = (SQLDatabaseTypeExtensionPoint) smanager.lookup(SQLDatabaseTypeExtensionPoint.ROLE);
063        _sqlDataSourceManager = (SQLDataSourceManager) manager.lookup(SQLDataSourceManager.ROLE);
064    }
065    
066    public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception
067    {
068        Map<String, Object> results = new HashMap<>();
069        
070        Request request = ObjectModelHelper.getRequest(objectModel);
071        request.setAttribute(JSonReader.OBJECT_TO_READ, results);
072        
073        String datasourceId = request.getParameter("datasourceId");
074        String tableName = request.getParameter("sqlTableName");
075        String limit = StringUtils.defaultString(request.getParameter("limit"), "10");
076        String offset = StringUtils.defaultString(request.getParameter("start"), "0");
077        String whereCondition = _getWhereCondition(request);
078        String sortCondition = _getSortCondition(request);
079        
080        Connection connection = null;
081        PreparedStatement stmt = null;
082        ResultSet rs = null;
083        try
084        {
085            connection = ConnectionHelper.getConnection(datasourceId);
086            String dbType = ConnectionHelper.getDatabaseType(connection);
087
088            String escapedTableName = _sqlDatabaseTypeExtensionPoint.languageEscapeTableName(dbType, tableName);
089            
090            // Count
091            stmt = connection.prepareStatement("SELECT count(*) FROM " + escapedTableName + whereCondition);
092            rs = stmt.executeQuery();
093
094            rs.next();
095            results.put("total", rs.getString(1));
096            
097            if (rs.getInt(1) < Integer.parseInt(offset))
098            {
099                offset = "0";
100            }
101            
102            ConnectionHelper.cleanup(rs);
103            ConnectionHelper.cleanup(stmt);
104            
105            stmt = connection.prepareStatement(_sqlDatabaseTypeExtensionPoint.languageLimitQuery(dbType, "SELECT * FROM " + escapedTableName + whereCondition + sortCondition, limit, offset));
106            rs = stmt.executeQuery();
107            
108
109            // Table structure
110            ResultSetMetaData metaData = rs.getMetaData();
111            results.put("meta", _getDataStructure(_getPrimaryKeys(connection.getMetaData(), tableName), metaData, datasourceId));
112
113            int columnCount = metaData.getColumnCount();
114            
115            List<Map<String, Object>> lines = new ArrayList<>();
116            results.put("data", lines);
117            while (rs.next())
118            {
119                Map<String, Object> line = new HashMap<>();
120                for (int i = 1; i <= columnCount; i++)
121                {
122                    String value = rs.getString(i);
123                    if (metaData.getColumnDisplaySize(i) > 255 && value != null && value.length() > 255)
124                    {
125                        line.put(metaData.getColumnName(i), value.substring(0, 255) + "…");
126                    }
127                    else
128                    {
129                        line.put(metaData.getColumnName(i), value);
130                    }
131                }
132                lines.add(line);
133            }
134        }
135        catch (Exception e)
136        {
137            throw new ProcessingException("An error occurred while getting SQL table '" + tableName + "' data for datasource '" + datasourceId + "'", e);
138        }
139        finally
140        {
141            ConnectionHelper.cleanup(rs);
142            ConnectionHelper.cleanup(stmt);
143            ConnectionHelper.cleanup(connection);
144        }
145        
146        results.put("success", "true");
147        
148        return EMPTY_MAP;
149    }
150    
151    private Map<String, Object> _getDataStructure(Set<String> primaryKeys, ResultSetMetaData metaData, String datasourceId) throws SQLException
152    {
153        Map<String, Object> results = new HashMap<>();
154        
155        results.put("datasource", _sqlDataSourceManager.getDataSourceDefinition(datasourceId).getName());
156
157        List<Map<String, String>> columns = new ArrayList<>();
158        results.put("columns", columns);
159
160        int columnCount = metaData.getColumnCount();
161        for (int i = 1; i <= columnCount; i++)
162        {
163            Map<String, String> column = new HashMap<>();
164            columns.add(column);
165            
166            column.put("name", metaData.getColumnName(i));
167            column.put("displaySize", Integer.toString(metaData.getColumnDisplaySize(i)));
168            column.put("label", metaData.getColumnLabel(i) 
169                    + "<br/>" +  metaData.getColumnTypeName(i) + " (" + metaData.getPrecision(i) + ")" 
170                    + (metaData.isNullable(i) == ResultSetMetaData.columnNoNulls ? " NOT NULL" : "")
171                    + (primaryKeys.contains(metaData.getColumnName(i)) ? "<br/>Primary Key" : ""));
172            column.put("type", metaData.getColumnTypeName(i));
173            column.put("isPrimary", Boolean.toString(primaryKeys.contains(metaData.getColumnName(i))));
174            column.put("isNullable", Boolean.toString(metaData.isNullable(i) == ResultSetMetaData.columnNoNulls));
175        }
176
177        results.put("columnsHashcode", columns.hashCode());
178                
179        return results;
180    }
181
182    private Set<String> _getPrimaryKeys(DatabaseMetaData databaseMetaData, String tableName) throws SQLException
183    {
184        Set<String> primaryKey = new HashSet<>();
185        ResultSet primaryKeys = databaseMetaData.getPrimaryKeys(null, null, tableName);
186        while (primaryKeys.next())
187        {
188            primaryKey.add(primaryKeys.getString("COLUMN_NAME"));
189        }
190        return primaryKey;
191    }
192
193    private String _getWhereCondition(Request request)
194    {
195        String whereCondition = StringUtils.defaultString(request.getParameter("where"));
196        if (StringUtils.isNotBlank(whereCondition))
197        {
198            whereCondition = " WHERE " + whereCondition;
199        }
200        return whereCondition;
201    }
202
203    private String _getSortCondition(Request request)
204    {
205        String sortCondition = StringUtils.defaultString(request.getParameter("sort"));
206        if (StringUtils.isNotBlank(sortCondition))
207        {
208            List<String> sorts = new ArrayList<>();
209            
210            List<Object> sortInfos = _jsonUtils.convertJsonToList(sortCondition);
211            for (Object sortInfo : sortInfos)
212            {
213                @SuppressWarnings("unchecked")
214                Map<String, String> sort = (Map<String, String>) sortInfo;
215                sorts.add(sort.get("property") + " " + sort.get("direction"));
216            }
217            
218            if (sorts.size() > 0)
219            {
220                sortCondition = " ORDER BY " + StringUtils.join(sorts, ", ");
221            }
222            else
223            {
224                sortCondition = "";
225            }
226        }
227        return sortCondition;
228    }
229}