|
|
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
Page 5 of 7
PathProxyService is the interface implemented by our PathProxyDao (this simple application doesn't really require a well-defined service layer). Listing 5 displays the PathProxyDao method we use to get children from a path.
public List<Object> getPathChildren(EntityPath path, String type){
PathProxy parentPp = this.getPathProxyFromMoArray(path.getPathAsObjects(), false);
if (log.isTraceEnabled()) { log.trace("parentPp: " + parentPp); }
List<Object> list = new ArrayList<Object>();
if (parentPp == null){
if (log.isInfoEnabled()) { log.info("No object found on path: " + ArrayUtils.toString(path)); }
} else {
String hql = "select t from " + type + " t, PathProxy pp where t.id = pp.entityId " +
" and pp.parent = :parentPp";
if (log.isTraceEnabled()) { log.trace("hql: " + hql); }
Map<String, Object> params = new HashMap<String, Object>();
params.put("parentPp", parentPp);
list = this.getJpaTemplate().findByNamedParams(hql, params);
}
if (log.isInfoEnabled()) { log.info("RETURN list: " + list); }
return list;
}
You can see this method is actually pretty simple. That's because most of the complexity is in the getPathProxyFromMoArray() method. Let's dive right into that, because it is the heart and soul of the PathProxy pattern's magic.
getPathProxyFromMoArray() is a fairly dense method. I'll list it all at once here and then refer back to it.
public PathProxy getPathProxyFromMoArray(ManagedObject[] moArray, boolean createIfNotFound){
if (log.isInfoEnabled()) { log.info("= = = = = = =BEGIN getPathProxyFromVoArray(): " + ArrayUtils.toString(moArray)); }
if (moArray == null || ArrayUtils.isEmpty(moArray)){
if (log.isInfoEnabled()) { log.info("Got an empty voArray, returning null."); }
return null;
}
// Build a query string that has as many name/id parameters as there are vo in the array
// Note, this may not work with entities using composite keys - hibernate issues invalid sql for null
StringBuffer queryString = new StringBuffer("select pp from PathProxy pp "
+ "where "); // (A)
if (moArray.length > 0) {
for (int i = moArray.length - 1; i >= 0; i--){ // (B)
if (log.isTraceEnabled()) { log.trace("Creating ppClause for VO: " + i + " : " + moArray[i]); }
StringBuffer ppClause = new StringBuffer("pp"); // (C)
// Add as many .parent's to the pp as we have counted into the hierarchy, this gets us the parent pp (eg, pp.parent.parent when i = 2)
for (int parentCount = moArray.length - 2; parentCount >= i; parentCount--){ // (D)
ppClause.append(".parent");
}
// (E)
if ((moArray[i] != null) && (!moArray[i].equals("null"))){ // If not null, add type and id
String ppEntityType = ppClause.toString() + "." + PathProxy.ENTITY_TYPE_PROPERTY + " = '" + moArray[i].getClass().getName() + "'";
// Here's where we benefit from using the ManagedObject base class ... no need to figure out what property to use for the ID
String ppEntityId = ppClause.toString() + "." + PathProxy.ENTITY_ID_PROPERTY + " = " + moArray[i].getId();
queryString.append(ppEntityId);
queryString.append(" and ");
queryString.append(ppEntityType);
if (i > 0){ // Add an 'and' if we will add more ppClauses to the queryString
queryString.append(" and "); // (F)
}
// (G)
} else { // This vo is null, so add tp.parent = null
// This is where Hibernate generates bad sql for composite keys, removing fixes that:
queryString.append(ppClause.toString() + " is null");
// Remove and added by last iteration (if you disable the 'is null')
// queryString.delete(queryString.length() - 4, queryString.length() ); //(" and ")
}
}
} else {
// Shouldn't get here
return null;
}
if (log.isTraceEnabled()) { log.trace("------ Here's the queryString: " + queryString + " ------"); }
List lst = this.getJpaTemplate().find(queryString.toString());
if (log.isTraceEnabled()) { log.trace("Query for path proxy with moArray returned: " + lst); }
PathProxy ppToReturn = null;
if (CollectionUtils.isEmpty(lst)) { // No path proxy there yet
if (createIfNotFound){
if (log.isInfoEnabled()) { log.info("No pathproxy on that path, creating new..."); }
ppToReturn = createPathProxy(moArray);
} else {
ppToReturn = null;
}
} else {
ppToReturn = (PathProxy)lst.get(0);
}
if (log.isTraceEnabled()) { log.trace("RETURN ppToReturn: " + ppToReturn); }
return ppToReturn;
}
First off, "Mo" stands for ManagedObject. So what we are saying with the call to getPathProxyFromMoArray() is, "Give me an array of ManagedObjects representing a path, and I'll give you back the PathProxy -- the one and only unique PathProxy -- which points to that node. This last point is important. There is at most one PathProxy for every unique path.
Notice that getPathProxyFromMoArray() also has a boolean switch to determine if a PathProxy should be generated if one is not found: createIfNotFound. For some operations it makes sense to create the PathProxy, for example, when you are adding a child to the path. If you are deleting or checking for the existence of children, however,
it does not make sense!
The core idea behind finding a PathProxy is building an HQL query (actually, a JPA-QL query) that will return us the unique PathProxy for the Object array. We do this by saying, "select from pathProxy pp where pp.entityType = ? and pp.entityId = ? and pp.parent.entityType = ? and pp.parent.entityId = ? ...". Essentially, as long as there are more parents in the array, we slap on another .parent.
I've added some footnotes to Listing 6, I'll quickly go over them here.
A: this line sets up the beginning of theQuery.B: counts backward from the
moArraybecause our path goes fromparent-->child. This is just a decision. You could go fromchild-->parent.C: begins a
StringBufferthat will hold our "PathProxyclause".D: adds on as many "
.parents" as needed. You'll notice that the first time through, there is no ".parent" added -- that's because we want the firstPathProxyto point directly to the first child. Basically, this innerfor loopcounts to 1 less than the number ofpathobjects the outer loop has traversed , adding a.parenteach time.So (not to belabor the point but), the first time through there are no
.parents added. The fifth time, there would be four:pp.parent.parent.parent.parent. The query adds a new part of thewhereclause that refers to each node in the path.E: shows a little logical structure that takes our
PathProxyclause and adds an.entityTypeand.entityIdto two differentStrings. Next, we add those together and add them to the query. If we're not at the end of the path yet, we append an "and" (see F). Also, because we have aManagedObjectto deal with, we can easily get the objects' ids (see inline comments).Stepping back, I want to point out that we're using the
Classnames for the objects as their type. This has a lot of synergy with JPA, which also uses theClassto identify objects. We could have added agetType()method toManagedObjectand used that. I've found, though, that using the classname is a good solution.G: shows an
elsethat handles anullin the path array. This should occur only at the end of the array. At the end of the query, it will add a "... and pp.parent.parent.parent is null". As the inline comments note, if you are dealing with composite keys, Hibernate doesn't output the correct SQL, and you will get an error. You can usually get away with leaving this off, so long as your root-level objects are unique for the paths in your system.
Now we're at the point where we can execute the query and see whether we have a PathProxy or not. If not, we'll either create one or return null, based on the createIfNotFound flag.
Archived Discussions (Read only)