/****************************************************************************** * Product: ADempiere ERP & CRM Smart Business Solution * * Copyright (C) 2009 www.metas.de * * This program is free software; you can redistribute it and/or modify it * * under the terms version 2 of the GNU General Public License as published * * by the Free Software Foundation. This program is distributed in the hope * * that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * * with this program; if not, write to the Free Software Foundation, Inc., * * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * *****************************************************************************/ package org.adempiere.model; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.logging.Level; import org.adempiere.exceptions.AdempiereException; import org.compiere.model.MAccount; import org.compiere.model.MAttributeSetInstance; import org.compiere.model.MChart; import org.compiere.model.MColumn; import org.compiere.model.MImage; import org.compiere.model.MLocation; import org.compiere.model.MLocator; import org.compiere.model.MQuery; import org.compiere.model.MResourceAssignment; import org.compiere.model.MRole; import org.compiere.model.MSysConfig; import org.compiere.model.MTab; import org.compiere.model.MTable; import org.compiere.model.PO; import org.compiere.util.CLogger; import org.compiere.util.DB; import org.compiere.util.DisplayType; import org.compiere.util.Env; import org.compiere.util.Util; /** * Generic provider of zoom targets. Contains pieces of {@link org.adempiere.webui.WZoomAcross} * methods getZoomTargets and addTarget. * * @author Tobias Schoeneberg, www.metas.de - FR [ 2897194 ] Advanced Zoom and RelationTypes * */ public class GenericZoomProvider implements IZoomProvider { private static final CLogger logger = CLogger.getCLogger(GenericZoomProvider.class); private Map queries; @Override public List retrieveZoomInfos(PO po) { // User preference boolean detailedZoom = "Y".equals(Env.getContext(Env.getCtx(), "P|IsDetailedZoomAcross")); StringBuilder sqlb = new StringBuilder().append( "SELECT w.AD_Window_ID, w.Name, tt.Name, f.Name, t.TableName, c.ColumnName, tt.AD_Tab_ID, "); boolean baseLanguage = Env.isBaseLanguage(Env.getCtx(), "AD_Window"); String tabAlias; if (baseLanguage) { tabAlias = "tt"; } else { tabAlias = "tt0"; } String justFirstTab = ""; if (! detailedZoom) { justFirstTab = " AND " + tabAlias + ".SeqNo=10 "; } if (baseLanguage) { sqlb.append( "tt.SeqNo " + "FROM AD_Table t " + "JOIN AD_Tab tt ON (tt.AD_Table_ID=t.AD_Table_ID AND tt.IsActive='Y' AND tt.Name NOT LIKE 'Used in%' AND tt.IsReadOnly='N' AND tt.IsSortTab='N'") .append(justFirstTab) .append(") " + "JOIN AD_Window w ON (tt.AD_Window_ID=w.AD_Window_ID AND w.IsActive='Y') " + "JOIN AD_Column c ON (t.AD_Table_ID=c.AD_Table_ID AND c.IsActive='Y' AND c.IsKey='N' AND c.IsParent='N' AND c.ColumnSQL IS NULL) " + "JOIN AD_Field f ON (f.AD_Column_ID=c.AD_Column_ID AND f.AD_Tab_ID=tt.AD_Tab_ID AND f.IsActive='Y' AND f.IsDisplayed='Y') "); } else { sqlb.append( "tt0.SeqNo " + "FROM AD_Table t " + "JOIN AD_Tab tt0 ON (tt0.AD_Table_ID=t.AD_Table_ID AND tt0.IsActive='Y' AND tt0.Name NOT LIKE 'Used in%' AND tt0.IsReadOnly='N' AND tt0.IsSortTab='N'") .append(justFirstTab) .append(") " + "JOIN AD_Tab_Trl tt ON (tt.AD_Tab_ID=tt0.AD_Tab_ID AND tt.AD_Language=?) " + "JOIN AD_Window w0 ON (tt0.AD_Window_ID=w0.AD_Window_ID AND w0.IsActive='Y') " + "JOIN AD_Window_Trl w ON (w.AD_Window_ID=w0.AD_Window_ID AND w.AD_Language=?) " + "JOIN AD_Column c ON (t.AD_Table_ID=c.AD_Table_ID AND c.IsActive='Y' AND c.IsKey='N' AND c.IsParent='N' AND c.ColumnSQL IS NULL) " + "JOIN AD_Field f0 ON (f0.AD_Column_ID=c.AD_Column_ID AND f0.AD_Tab_ID=tt0.AD_Tab_ID AND f0.IsActive='Y' AND f0.IsDisplayed='Y') " + "JOIN AD_Field_Trl f ON (f.AD_Field_ID=f0.AD_Field_ID AND f.AD_Language=?) "); } sqlb.append( "LEFT JOIN AD_Ref_Table r ON (c.AD_Reference_Value_ID=r.AD_Reference_ID) " + "LEFT JOIN AD_Table tr ON (r.AD_Table_ID=tr.AD_Table_ID) " + "WHERE t.IsActive='Y' " + " AND t.TableName NOT LIKE 'I|_%' ESCAPE '|' " // not import tables + " AND t.TableName NOT LIKE 'T|_%' ESCAPE '|' " // not temp tables + " AND t.IsView='N' "); // not views if (detailedZoom) { sqlb.append( " AND ( ( c.ColumnName=? AND c.AD_Reference_ID IN (?,?)) "); // TableDir/TableDirUU if (MLocation.COLUMNNAME_C_Location_ID.equals(po.get_KeyColumns()[0])) sqlb.append(" OR c.AD_Reference_ID=").append(DisplayType.Location); else if (MAccount.COLUMNNAME_C_ValidCombination_ID.equals(po.get_KeyColumns()[0])) sqlb.append(" OR c.AD_Reference_ID=").append(DisplayType.Account); else if (MLocator.COLUMNNAME_M_Locator_ID.equals(po.get_KeyColumns()[0])) sqlb.append(" OR c.AD_Reference_ID=").append(DisplayType.Locator); else if (MImage.COLUMNNAME_AD_Image_ID.equals(po.get_KeyColumns()[0])) sqlb.append(" OR c.AD_Reference_ID=").append(DisplayType.Image); else if (MResourceAssignment.COLUMNNAME_S_ResourceAssignment_ID.equals(po.get_KeyColumns()[0])) sqlb.append(" OR c.AD_Reference_ID=").append(DisplayType.Assignment); else if (MAttributeSetInstance.COLUMNNAME_M_AttributeSetInstance_ID.equals(po.get_KeyColumns()[0])) sqlb.append(" OR c.AD_Reference_ID=").append(DisplayType.PAttribute); else if (MChart.COLUMNNAME_AD_Chart_ID.equals(po.get_KeyColumns()[0])) sqlb.append(" OR c.AD_Reference_ID=").append(DisplayType.Chart); sqlb.append(" OR ( c.ColumnName=? AND c.AD_Reference_ID IN (?,?) AND c.AD_Reference_Value_ID IS NULL ) " // Search/SearchUU + " OR ( c.AD_Reference_ID IN (?,?,?,?) AND c.AD_Reference_Value_ID=r.AD_Reference_ID AND tr.TableName=? ) ) "); // Table/Search/Table/SearchUU } else { sqlb.append(" AND c.ColumnName=? "); } sqlb.append(" ORDER BY 2, 8"); final PreparedStatement pstmt = DB.prepareStatement(sqlb.toString(), null); ResultSet rs = null; try { int index = 1; if (!baseLanguage) { pstmt.setString(index++, Env.getAD_Language(Env.getCtx())); pstmt.setString(index++, Env.getAD_Language(Env.getCtx())); pstmt.setString(index++, Env.getAD_Language(Env.getCtx())); } pstmt.setString(index++, po.get_KeyColumns()[0]); if (detailedZoom) { pstmt.setInt(index++, DisplayType.TableDir); pstmt.setInt(index++, DisplayType.TableDirUU); pstmt.setString(index++, po.get_KeyColumns()[0]); pstmt.setInt(index++, DisplayType.Search); pstmt.setInt(index++, DisplayType.SearchUU); pstmt.setInt(index++, DisplayType.Table); pstmt.setInt(index++, DisplayType.TableUU); pstmt.setInt(index++, DisplayType.Search); pstmt.setInt(index++, DisplayType.SearchUU); pstmt.setString(index++, po.get_TableName()); } rs = pstmt.executeQuery(); final List result = new ArrayList(); queries = new HashMap(); while (rs.next()) { int AD_Window_ID = rs.getInt(1); String winName = rs.getString(2); String tabName = rs.getString(3); String fldName = rs.getString(4); String targetTableName = rs.getString(5); String targetColumnName = rs.getString(6); int AD_Tab_ID = rs.getInt(7); Boolean access = MRole.getDefault().getWindowAccess(AD_Window_ID); if (access == null) continue; final MQuery query = evaluateQuery(targetTableName, targetColumnName, AD_Tab_ID, po); if (query != null && query.getRecordCount() > 0) { if (detailedZoom) { result.add(new ZoomInfoFactory.ZoomInfo(AD_Window_ID, query, winName + " / " + tabName + " / " + fldName)); } else { result.add(new ZoomInfoFactory.ZoomInfo(AD_Window_ID, query, winName)); } } } queries = null; return result; } catch (SQLException e) { logger.log(Level.SEVERE, sqlb.toString(), e); throw new AdempiereException(e); } finally { DB.close(rs, pstmt); } } /** * * @param targetTableName * @param targetColumnName * @param AD_Tab_ID * @param po * @return MQuery */ private MQuery evaluateQuery(String targetTableName, String targetColumnName, int AD_Tab_ID, final PO po) { Properties ctx = Env.getCtx(); int clientID = Env.getAD_Client_ID(ctx); final MQuery query = new MQuery(); MTable table = MTable.get(ctx, targetTableName); if (! table.columnExistsInDB("AD_Client_ID")) // table doesn't have AD_Client_ID return null; int tabIDLoop = AD_Tab_ID; int levelUp = 0; while (true) { MTab tab = MTab.get(tabIDLoop); String whereCtx = tab.getWhereClause(); if (!Util.isEmpty(whereCtx, true)) { if (whereCtx.indexOf("@") != -1) whereCtx = Env.parseVariable(whereCtx, po, null, true); if (whereCtx.indexOf("@") != -1) // could not parse - probably window context variable in where tab return null; if (levelUp == 0) { query.addRestriction("(" + whereCtx + ")"); } else if (levelUp == 1) { MTable parentTable = MTable.get(ctx, tab.getAD_Table_ID()); String parentTableName = parentTable.getTableName(); StringBuilder subquery = new StringBuilder() .append(parentTableName) .append("_ID IN (SELECT ") .append(parentTableName) .append("_ID FROM ") .append(parentTableName) .append(" WHERE ") .append(whereCtx) .append(")"); query.addRestriction("(" + subquery + ")"); } else { // Cannot add where beyond the first parent - need to implement recursion return null; } } levelUp++; tabIDLoop = tab.getParentTabID(); if (tabIDLoop < 0) break; } MColumn column = table.getColumn(targetColumnName); String refTableName = column.getReferenceTableName(); MTable refTable = MTable.get(ctx, refTableName); StringBuilder restriction = new StringBuilder(targetTableName) .append(".") .append(targetColumnName) .append("="); if (refTable.isUUIDKeyTable()) { restriction.append(DB.TO_STRING(po.get_UUID())); query.setZoomValue(po.get_UUID()); } else { restriction.append(po.get_ID()); query.setZoomValue(po.get_ID()); } query.addRestriction(restriction.toString()); query.setZoomTableName(targetTableName); query.setZoomColumnName(targetColumnName); String accessLevel = table.getAccessLevel(); if ( clientID != 0 && MTable.ACCESSLEVEL_SystemOnly.equals(accessLevel)) { return null; } if ( clientID != 0 && ( MTable.ACCESSLEVEL_All.equals(accessLevel) || MTable.ACCESSLEVEL_SystemPlusClient.equals(accessLevel))) { query.addRestriction(targetTableName+".AD_Client_ID IN (0, " + clientID + ")"); } else { query.addRestriction(targetTableName+".AD_Client_ID=" + clientID); } StringBuilder sqlb = new StringBuilder("SELECT COUNT(*) FROM ") .append(targetTableName) .append(" WHERE ") .append(query.getWhereClause(true)); String sql = sqlb.toString(); int count = -1; if (queries.containsKey(sql)) { count = queries.get(sql); } else { int timeout = MSysConfig.getIntValue(MSysConfig.ZOOM_ACROSS_QUERY_TIMEOUT, 5, Env.getAD_Client_ID(Env.getCtx())); // default 5 seconds count = getSQLValueTimeout(null, sql, timeout); queries.put(sql, count); } query.setRecordCount(count); return query; } /** * @param object * @param sql * @param timeOut * @return sql value from DB */ private int getSQLValueTimeout(Object object, String sql, int timeOut) { int retValue = -1; PreparedStatement pstmt = null; ResultSet rs = null; try { pstmt = DB.prepareStatement(sql, null); if (timeOut > 0) pstmt.setQueryTimeout(timeOut); rs = pstmt.executeQuery(); if (rs.next()) retValue = rs.getInt(1); } catch (SQLException e) { logger.warning(e.getMessage() + " -> " + sql); } finally { DB.close(rs, pstmt); rs = null; pstmt = null; } return retValue; } }