001// -------------------------------------------------------------------------------- 002// Copyright 2002-2024 Echo Three, LLC 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// -------------------------------------------------------------------------------- 016 017package com.echothree.util.server.persistence; 018 019import com.echothree.util.common.exception.PersistenceDatabaseException; 020import com.echothree.util.common.persistence.BasePK; 021import com.google.common.base.Joiner; 022import com.google.common.base.Splitter; 023import java.lang.reflect.Constructor; 024import java.lang.reflect.InvocationTargetException; 025import java.lang.reflect.Method; 026import java.lang.reflect.ParameterizedType; 027import java.sql.ResultSet; 028import java.sql.SQLException; 029import java.util.ArrayList; 030import java.util.List; 031 032public abstract class BaseDatabaseQuery<R extends BaseDatabaseResult> { 033 034 protected final String sql; 035 protected final EntityPermission entityPermission; 036 037 protected final Class<R> baseDatabaseResultClass; 038 039 List<DatabaseResultMethod> databaseResultMethods; 040 041 @SuppressWarnings("unchecked") 042 protected BaseDatabaseQuery(final String sql, final EntityPermission entityPermission) { 043 this.sql = sql; 044 this.entityPermission = entityPermission; 045 046 this.baseDatabaseResultClass = (Class<R>)((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0]; 047 } 048 049 private DatabaseResultMethod getDatabaseResultMethod(final String columnLabel) { 050 DatabaseResultMethod databaseResultMethod; 051 052 try { 053 final var getMethod = baseDatabaseResultClass.getMethod("get" + columnLabel); 054 final var returnType = getMethod.getReturnType(); 055 final var setMethod = baseDatabaseResultClass.getMethod("set" + columnLabel, returnType); 056 Object factoryInstance = null; 057 Constructor<?> pkConstructor = null; 058 Method getEntityFromPkMethod = null; 059 060 if(!returnType.equals(Long.class) && !returnType.equals(Integer.class) && !returnType.equals(String.class)) { 061 final var superclass = returnType.getSuperclass(); 062 063 if(superclass != null) { 064 if(superclass.equals(BasePK.class)) { 065 pkConstructor = returnType.getDeclaredConstructor(Long.class); 066 } else if(superclass.equals(BaseEntity.class)) { 067 final var name = returnType.getName(); 068 final var nameComponents = Splitter.on('.').trimResults().omitEmptyStrings().splitToList(name).toArray(new String[0]); 069 String factoryName = null; 070 String pkName = null; 071 072 for(int i = 0 ; i < nameComponents.length ; i++) { 073 if(nameComponents[i].equals("server")) { 074 int j = i; 075 String baseClassName = nameComponents[i + 2]; 076 077 nameComponents[++i] = "factory"; 078 nameComponents[++i] = baseClassName + "Factory"; 079 factoryName = Joiner.on(".").join(nameComponents); 080 081 nameComponents[j++] = "common"; 082 nameComponents[j++] = "pk"; 083 nameComponents[j] = baseClassName + "PK"; 084 pkName = Joiner.on(".").join(nameComponents); 085 086 break; 087 } 088 } 089 090 try { 091 final var classLoader = returnType.getClassLoader(); 092 final var pkType = classLoader.loadClass(pkName); 093 final var factoryType = classLoader.loadClass(factoryName); 094 final var getInstanceMethod = factoryType.getDeclaredMethod("getInstance"); 095 096 factoryInstance = getInstanceMethod.invoke(null); 097 pkConstructor = pkType.getDeclaredConstructor(Long.class); 098 getEntityFromPkMethod = factoryType.getDeclaredMethod("getEntityFromPK", Session.class, EntityPermission.class, pkType); 099 } catch(ClassNotFoundException | IllegalAccessException | InvocationTargetException ex) { 100 throw new PersistenceDatabaseException(ex); 101 } 102 } 103 } 104 105 if(pkConstructor == null) { 106 throw new PersistenceDatabaseException("unsupported Class in getDatabaseResultMethod, " + returnType); 107 } 108 } 109 110 databaseResultMethod = new DatabaseResultMethod(setMethod, returnType, factoryInstance, pkConstructor, getEntityFromPkMethod); 111 } catch(NoSuchMethodException nsme) { 112 throw new PersistenceDatabaseException(nsme); 113 } 114 115 return databaseResultMethod; 116 } 117 118 private List<DatabaseResultMethod> getDatabaseResultMethods(final ResultSet rs) { 119 if(databaseResultMethods == null) { 120 try { 121 final var rsmd = rs.getMetaData(); 122 123 databaseResultMethods = new ArrayList<>(); 124 for(int columnIndex = 1; columnIndex <= rsmd.getColumnCount(); columnIndex++) { 125 databaseResultMethods.add(getDatabaseResultMethod(rsmd.getColumnLabel(columnIndex))); 126 } 127 } catch(SQLException se) { 128 throw new PersistenceDatabaseException(se); 129 } 130 } 131 132 return databaseResultMethods; 133 } 134 135 protected List<R> execute(final Object... params) { 136 final var results = new ArrayList<R>(); 137 final var session = ThreadSession.currentSession(); 138 final var ps = session.prepareStatement(sql); 139 140 Session.setQueryParams(ps, params); 141 142 try { 143 ps.execute(); 144 145 try(final var rs = ps.getResultSet()) { 146 while(rs.next()) { 147 int columnIndex = 0; 148 149 try { 150 final var baseDatabaseResult = baseDatabaseResultClass.getDeclaredConstructor().newInstance(); 151 152 for(DatabaseResultMethod databaseResultMethod : getDatabaseResultMethods(rs)) { 153 final var type = databaseResultMethod.type; 154 Object param = null; 155 156 columnIndex++; 157 if(type.equals(Long.class)) { 158 param = rs.getLong(columnIndex); 159 } else if(type.equals(Integer.class)) { 160 param = rs.getInt(columnIndex); 161 } else if(type.equals(String.class)) { 162 param = rs.getString(columnIndex); 163 } else { 164 final var superclass = type.getSuperclass(); 165 166 if(superclass != null) { 167 if(superclass.equals(BasePK.class)) { 168 param = databaseResultMethod.pkConstructor.newInstance(rs.getLong(columnIndex)); 169 } else if(superclass.equals(BaseEntity.class)) { 170 Object pk = databaseResultMethod.pkConstructor.newInstance(rs.getLong(columnIndex)); 171 172 param = databaseResultMethod.getEntityFromPkMethod.invoke(databaseResultMethod.factoryInstance, session, entityPermission, pk); 173 } 174 } 175 176 if(param == null) { 177 throw new PersistenceDatabaseException("unsupported Class in execute, " + type); 178 } 179 } 180 181 databaseResultMethod.setMethod.invoke(baseDatabaseResult, param); 182 } 183 184 results.add(baseDatabaseResult); 185 } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | 186 InvocationTargetException ex) { 187 throw new PersistenceDatabaseException(ex); 188 } 189 } 190 } 191 } catch (SQLException se) { 192 throw new PersistenceDatabaseException(se); 193 } 194 195 return results; 196 } 197 198}