View Javadoc
1   /*
2    * Copyright 2001-2008 The Apache Software Foundation.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   *
16   */
17  
18  package org.apache.juddi.query;
19  
20  import java.util.ArrayList;
21  import java.util.Collections;
22  import java.util.HashSet;
23  import java.util.List;
24  import java.util.Set;
25  
26  import javax.persistence.EntityManager;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  import org.apache.juddi.config.Constants;
31  import org.apache.juddi.query.util.DynamicQuery;
32  import org.apache.juddi.query.util.FindQualifiers;
33  import org.apache.juddi.query.util.KeyedRefGroupTModelComparator;
34  import org.uddi.api_v3.CategoryBag;
35  import org.uddi.api_v3.KeyedReference;
36  import org.uddi.api_v3.KeyedReferenceGroup;
37  
38  /**
39   * Returns the list of "entity" keys possessing the keyedReferenceGroups in the passed category bag.
40   * Output is restricted by list of "entity" keys passed in.  If null, all entities are searched.
41   * Output is produced by building the appropriate JPA query based on input and find qualifiers.
42   * 
43   * NOTES:
44   * 1) Category groups are grouped with a logical AND by default.
45   * 2) Concerning when the categories are AND'd together - the only way this can be done with a single query was to create a self-join for 
46   *    each category.  If there are a lot of categories, the performance could suffer.
47   *    TODO:  Test performance with multiple AND'd categories.  If too slow, look to process this query in multiple steps.
48   * 3) The "orLikeKeys" qualifier complicates matters.  The "like" keys are OR'd together and these groups of "like" keys are AND'd together.
49   *    As with "andAllKeys", self-joins are created but only one for each group of "like" keys.  If none of the keyedReferences passed are alike then this
50   *    will reduce to an "andAllKeys" query.  If all are alike, then this will query will exhibit the behavior of OR'ing all keys.
51   * 
52   * @author <a href="mailto:jfaath@apache.org">Jeff Faath</a>
53   */
54  public class FindEntityByCategoryGroupQuery extends EntityQuery {
55  	
56  	@SuppressWarnings("unused")
57  	private final static Log log = LogFactory.getLog(FindEntityByCategoryGroupQuery.class);
58  
59  	private static final String ENTITY_KEYEDREFERENCEGROUP = "KeyedReferenceGroup";
60  	private static final String ALIAS_KEYEDREFERENCEGROUP = "krg";
61  	private static final String FIELD_CATEGORYBAG = "categoryBag";
62  
63  	private static final String ENTITY_KEYEDREFERENCE = "KeyedReference";
64  	private static final String ALIAS_KEYEDREFERENCE = buildAlias(ENTITY_KEYEDREFERENCE);
65  	private static final String FIELD_KEYEDREFERENCEGROUP = "keyedReferenceGroup";
66  	
67  	private final String entityName;
68  	private final String entityAlias;
69  	private final String keyName;
70  	private final String entityField;
71  	private final String entityNameChild;
72  	private final String entityAliasChild;
73  	private final String selectSQL;
74  	private String signaturePresent;
75  
76  	public FindEntityByCategoryGroupQuery(String entityName, String entityAlias, String keyName, 
77  			String entityField, String entityNameChild, String signaturePresent) {
78  		this.entityName = entityName;
79  		this.entityAlias = entityAlias;
80  		this.keyName = keyName;
81  		this.entityField = entityField;
82  		this.entityNameChild = entityNameChild;
83  		this.entityAliasChild = buildAlias(entityNameChild);
84  		this.signaturePresent = signaturePresent;
85  		
86  		StringBuffer sql = new StringBuffer(200);
87  		sql.append("select distinct " + entityAlias + "." + keyName + " from " 
88  				   + entityName + " " + entityAlias + " , " 
89  				   + entityNameChild + " " + entityAliasChild + " , "
90  				   + ENTITY_KEYEDREFERENCEGROUP + " " + ALIAS_KEYEDREFERENCEGROUP + " ");
91  		selectSQL = sql.toString();
92  	}
93  	
94  	public String getEntityName() {
95  		return entityName;
96  	}
97  
98  	public String getEntityAlias() {
99  		return entityAlias;
100 	}
101 
102 	public String getKeyName() {
103 		return keyName;
104 	}
105 
106 	public String getEntityField() {
107 		return entityField;
108 	}
109 
110 	public String getEntityNameChild() {
111 		return entityNameChild;
112 	}
113 	
114 	public String getEntityAliasChild() {
115 		return entityAliasChild;
116 	}
117 	
118 	public String getSelectSQL() {
119 		return selectSQL;
120 	}
121 	
122 	public String getSignaturePresent() {
123 		return signaturePresent;
124 	}
125 
126 	public void setSignaturePresent(String signaturePresent) {
127 		this.signaturePresent = signaturePresent;
128 	}
129 	
130 	@SuppressWarnings("unchecked")
131 	public List<Object> select(EntityManager em, FindQualifiers fq, CategoryBag categoryBag, List<Object> keysIn, DynamicQuery.Parameter... restrictions) {
132 		// If keysIn is not null and empty, then search is over.
133 		if ((keysIn != null) && (keysIn.size() == 0))
134 			return keysIn;
135 		
136 		if (categoryBag == null)
137 			return keysIn;
138 		
139 		List<KeyedReferenceGroup> categories = categoryBag.getKeyedReferenceGroup();
140 		if (categories == null || categories.size() == 0)
141 			return keysIn;
142 		
143 		List<KeyedReferenceGroup> keyedRefGroups = new ArrayList<KeyedReferenceGroup>(0);
144 		for (KeyedReferenceGroup elem : categories) {
145 			if (elem instanceof KeyedReferenceGroup)
146 				keyedRefGroups.add((KeyedReferenceGroup)elem);
147 		}
148 		if (keyedRefGroups.size() == 0)
149 			return keysIn;		
150 		
151 		
152 		Collections.sort(keyedRefGroups, new KeyedRefGroupTModelComparator());
153 		int count = 0;
154 		String prevTModelKey = null;
155 		Set<Object> orResults = new HashSet<Object>(0);
156 		List<Object> restrictionList = keysIn;
157 		List<Object> curResult = null;
158 		for (KeyedReferenceGroup keyedRefGroup : keyedRefGroups) {
159 			String curTModelKey = keyedRefGroup.getTModelKey();
160 			
161 			DynamicQuery dynamicQry = new DynamicQuery(selectSQL);
162 			appendConditions(dynamicQry, fq, keyedRefGroup);
163 			if (restrictions != null && restrictions.length > 0)
164 				dynamicQry.AND().pad().appendGroupedAnd(restrictions);
165 			
166 
167 			if (fq.isOrLikeKeys()) {
168 				if (!curTModelKey.equals(prevTModelKey)) {
169 					if (count != 0) {
170 						restrictionList = new ArrayList<Object>(orResults);
171 						orResults.clear();
172 					}
173 				}
174 			}
175 			else if (!fq.isOrAllKeys()) {
176 				if (count != 0)
177 					restrictionList = curResult;
178 			}
179 
180 			if (restrictionList != null && restrictionList.size() == 0)
181 				break;
182 			
183 			curResult = getQueryResult(em, dynamicQry, restrictionList, entityAlias + "." + keyName);
184 
185 			if (fq.isOrAllKeys() || fq.isOrLikeKeys()) {
186 				orResults.addAll((List<Object>)curResult);
187 			}
188 			
189 			prevTModelKey = curTModelKey;
190 			count++;
191 		}
192 		
193 		List<Object> result = null;
194 		if (fq.isOrAllKeys() || fq.isOrLikeKeys()) {
195 			result = new ArrayList<Object>(0);
196 			result.addAll(orResults);
197 		}
198 		else
199 			result = (List<Object>)curResult;
200 		
201 		return result;
202 		
203 	}
204 
205 	/*
206 	 * Appends the conditions to the query based on the keyedReferenceGroup.  According to the specification, a keyedReference group matches if all
207 	 * keyedReferences within the group are a subset of the target entity's group.  Thus, 
208 	 */
209 	public void appendConditions(DynamicQuery qry, FindQualifiers fq, KeyedReferenceGroup keyedRefGroup) {
210 		
211 		// Append the necessary tables (two will always be added connecting the entity to its category bag table and then the category table to the keyed reference group).
212 		appendJoinTables(qry, fq, keyedRefGroup);
213 
214 		// First, appending the group's tmodel key as a condition
215 		qry.AND().pad().appendGroupedAnd(new DynamicQuery.Parameter(ALIAS_KEYEDREFERENCEGROUP + ".tmodelKey", keyedRefGroup.getTModelKey(), DynamicQuery.PREDICATE_EQUALS));
216 		
217 		List<KeyedReference> keyedRefs = keyedRefGroup.getKeyedReference();
218 		if (keyedRefs != null && keyedRefs.size() > 0) {
219 		
220 			qry.AND().pad().openParen().pad();
221 	
222 			String predicate = DynamicQuery.PREDICATE_EQUALS;
223 			if (fq.isApproximateMatch()) {
224 				predicate = DynamicQuery.PREDICATE_LIKE;
225 			}
226 			
227 			int count = 0;
228 			int tblCount = -1;
229 			for(KeyedReference keyedRef : keyedRefs) {
230 				String tmodelKey = keyedRef.getTModelKey();
231 				String keyValue = keyedRef.getKeyValue();
232 				String keyName = keyedRef.getKeyName();
233 
234 				if (fq.isApproximateMatch()) {
235 					// JUDDI-235: wildcards are provided by user (only commenting in case a new interpretation arises)
236 					//keyValue = keyValue.endsWith(DynamicQuery.WILDCARD)?keyValue:keyValue + DynamicQuery.WILDCARD;
237 					//keyName = keyName.endsWith(DynamicQuery.WILDCARD)?keyName:keyName + DynamicQuery.WILDCARD;
238 				}
239 
240 				tblCount++;
241 				String keyValueTerm = ALIAS_KEYEDREFERENCE + tblCount + ".keyValue";
242 				String keyNameTerm = ALIAS_KEYEDREFERENCE + tblCount + ".keyName";
243 				String tmodelKeyTerm = ALIAS_KEYEDREFERENCE + tblCount + ".tmodelKeyRef";
244 				if (fq.isCaseInsensitiveMatch()) {
245 					keyValueTerm = "upper(" + keyValueTerm + ")";
246 					keyValue = keyValue.toUpperCase();
247 					
248 					keyNameTerm = "upper(" + keyNameTerm + ")";
249 					keyName = keyName.toUpperCase();
250 				}
251 				
252 				// According to specification, if the "general keyword" tmodel is used, then the keyName must be part of the query.
253 				if (Constants.GENERAL_KEYWORD_TMODEL.equalsIgnoreCase(tmodelKey)) {
254 					qry.appendGroupedAnd(new DynamicQuery.Parameter(tmodelKeyTerm, tmodelKey, DynamicQuery.PREDICATE_EQUALS),
255 										 new DynamicQuery.Parameter(keyValueTerm, keyValue, predicate),
256 										 new DynamicQuery.Parameter(keyNameTerm, keyName, predicate));
257 				}
258 				else {
259 					qry.appendGroupedAnd(new DynamicQuery.Parameter(tmodelKeyTerm, tmodelKey, DynamicQuery.PREDICATE_EQUALS),
260 										 new DynamicQuery.Parameter(keyValueTerm, keyValue, predicate));
261 					
262 				}
263 				
264 				if (count + 1 < keyedRefs.size())
265 					qry.AND().pad();
266 				
267 				count++;
268 			}
269 			qry.closeParen().pad();
270 		}
271 		
272 	}
273 
274 	
275 	
276 	/*
277 	 * Appends the necessary join table for the child entity and additional tables for when keys are AND'd.  When "orLikeKeys" is used, 
278 	 * we only need an extra table for each distinct tmodelKey.
279 	 */
280 	public void appendJoinTables(DynamicQuery qry, FindQualifiers fq, KeyedReferenceGroup keyedRefGroup) {
281 		
282 		if (keyedRefGroup != null) {
283 			
284 			List<KeyedReference> keyedRefs = keyedRefGroup.getKeyedReference();
285 			StringBuffer thetaJoins = new StringBuffer(200);
286 			if (keyedRefs != null && keyedRefs.size() > 0) {
287 				int tblCount = 0;
288 				for(int count = 0; count<keyedRefs.size(); count++) {
289 					if (count != 0) {
290 						tblCount++;
291 						qry.comma().pad().append(ENTITY_KEYEDREFERENCE + " " + ALIAS_KEYEDREFERENCE + tblCount).pad();
292 						thetaJoins.append(ALIAS_KEYEDREFERENCE + (tblCount - 1) + "." + FIELD_KEYEDREFERENCEGROUP + ".id = " + ALIAS_KEYEDREFERENCE + tblCount + "." + FIELD_KEYEDREFERENCEGROUP + ".id ");
293 						thetaJoins.append(DynamicQuery.OPERATOR_AND + " ");
294 					} else {
295 						qry.comma().pad().append(ENTITY_KEYEDREFERENCE + " " + ALIAS_KEYEDREFERENCE + tblCount).pad();
296 						thetaJoins.append(ALIAS_KEYEDREFERENCEGROUP + ".id = " + ALIAS_KEYEDREFERENCE + tblCount + "." + FIELD_KEYEDREFERENCEGROUP + ".id ");
297 						thetaJoins.append(DynamicQuery.OPERATOR_AND + " ");
298 					}
299 				}
300 			}
301 			qry.WHERE().pad().openParen().pad();
302 			
303 			// Appending the middling entity-specific category table condition
304 			qry.append(entityAlias + "." + keyName + " = " + entityAliasChild + "." + entityField + "." + KEY_NAME).pad();
305 			qry.AND().pad();
306 
307 			// Now, appending the condition that attaches the keyed reference group table
308 			qry.append(entityAliasChild + ".id = " + ALIAS_KEYEDREFERENCEGROUP + "." + FIELD_CATEGORYBAG + ".id").pad();
309 
310 			String thetaJoinsStr = thetaJoins.toString();
311 			if (thetaJoinsStr != null && thetaJoinsStr.length() > 0)
312 				qry.AND().pad();
313 			
314 			if (thetaJoinsStr.endsWith(DynamicQuery.OPERATOR_AND + " "))
315 				thetaJoinsStr = thetaJoinsStr.substring(0, thetaJoinsStr.length() - (DynamicQuery.OPERATOR_AND + " ").length());
316 			qry.append(thetaJoinsStr);
317 
318 			qry.closeParen().pad();
319 			if (fq!=null && fq.isSignaturePresent()) {
320 				qry.AND().pad().openParen().pad().append(getSignaturePresent()).pad().closeParen().pad();
321 			}
322 		}
323 	}
324 
325 }