/*
* Copyright (C) NetStruxr, Inc. All rights reserved.
*
* This software is published under the terms of the NetStruxr
* Public Software License version 0.5, a copy of which has been
* included with this distribution in the LICENSE.NPL file. */
package er.directtoweb.pages;
import java.util.Enumeration;
import com.webobjects.appserver.WOComponent;
import com.webobjects.appserver.WOContext;
import com.webobjects.appserver.WODisplayGroup;
import com.webobjects.appserver.WORequest;
import com.webobjects.appserver.WOResponse;
import com.webobjects.directtoweb.D2W;
import com.webobjects.directtoweb.D2WContext;
import com.webobjects.directtoweb.ListPageInterface;
import com.webobjects.directtoweb.NextPageDelegate;
import com.webobjects.eoaccess.EODatabaseDataSource;
import com.webobjects.eoaccess.EOEntity;
import com.webobjects.eocontrol.EOAndQualifier;
import com.webobjects.eocontrol.EODataSource;
import com.webobjects.eocontrol.EOFetchSpecification;
import com.webobjects.eocontrol.EOQualifier;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSDictionary;
import com.webobjects.foundation.NSKeyValueCoding;
import com.webobjects.foundation.NSMutableArray;
import com.webobjects.foundation.NSMutableDictionary;
import com.webobjects.foundation.NSValidation;
import er.directtoweb.delegates.ERDQueryDataSourceDelegateInterface;
import er.directtoweb.delegates.ERDQueryValidationDelegate;
import er.directtoweb.interfaces.ERDQueryPageInterface;
import er.extensions.appserver.ERXDisplayGroup;
import er.extensions.appserver.ERXResponseRewriter;
import er.extensions.foundation.ERXValueUtilities;
import er.extensions.localization.ERXLocalizer;
/**
* Superclass for all query pages.
* <p>
* In addition to the rest of the goodies of ERD2WPage, it lets you save and
* restore the initial query bindings by supplying a NS(Mutable)Dictionary which
* contains the keys "queryMin", "queryMax" etc from the respective fields of
* the WODisplayGroup.
* @d2wKey fetchSpecificationName
* @d2wKey enableQueryForNullValues
* @d2wKey isDeep
* @d2wKey usesDistinct
* @d2wKey refrehRefetchedObjects
* @d2wKey fetchLimit
* @d2wKey prefetchingRelationshipKeyPaths
* @d2wKey showListInSamePage
* @d2wKey listConfigurationName
* @d2wKey queryDataSourceDelegate
* @d2wKey queryValidationDelegate
* @d2wKey enableQueryForNullValues
* @d2wKey canQueryPropertyForNullValues
*/
public class ERD2WQueryPage extends ERD2WPage implements ERDQueryPageInterface {
/**
* Do I need to update serialVersionUID?
* See section 5.6 <cite>Type Changes Affecting Serialization</cite> on page 51 of the
* <a href="http://java.sun.com/j2se/1.4/pdf/serial-spec.pdf">Java Object Serialization Spec</a>
*/
private static final long serialVersionUID = 1L;
protected WODisplayGroup displayGroup;
protected boolean didLoadQueryBindings;
protected NSDictionary queryBindings;
protected EOFetchSpecification fetchSpecification;
protected ERDQueryDataSourceDelegateInterface queryDataSourceDelegate;
protected ERDQueryValidationDelegate queryValidationDelegate;
protected NSArray _nullablePropertyKeys;
protected NSMutableDictionary keysToQueryForNull = new NSMutableDictionary();
public ERD2WQueryPage(WOContext context) {
super(context);
createDisplayGroup();
}
protected void createDisplayGroup() {
displayGroup = new ERXDisplayGroup();
}
protected void pullQueryBindingsForName(String name) {
NSDictionary queryBindings = queryBindings();
if (queryBindings != null) {
NSDictionary source = (NSDictionary) queryBindings.objectForKey(name);
if (source != null) {
NSMutableDictionary destination = (NSMutableDictionary) NSKeyValueCoding.Utility.valueForKey(displayGroup, name);
destination.addEntriesFromDictionary(source);
}
}
}
/**
* <span class="ja">
* ディスプレイ・グループの全クエリ設定を取り除きます。
*
* @return カレント・ページ
* </span>
*/
public WOComponent clearAction() {
displayGroup().queryBindings().removeAllObjects();
displayGroup().queryMin().removeAllObjects();
displayGroup().queryMax().removeAllObjects();
displayGroup().queryOperator().removeAllObjects();
displayGroup().queryMatch().removeAllObjects();
if (displayGroup() instanceof ERXDisplayGroup) {
ERXDisplayGroup dg = (ERXDisplayGroup) displayGroup();
dg.clearExtraQualifiers();
}
return context().page();
}
public EOFetchSpecification fetchSpecification() {
if(fetchSpecification == null) {
String name = fetchSpecificationName();
if(name != null) {
fetchSpecification = entity().fetchSpecificationNamed(name);
}
}
return fetchSpecification;
}
public void setFetchSpecification(EOFetchSpecification value) {
fetchSpecification=value;
if(fetchSpecification != null) {
d2wContext().takeValueForKey(value.qualifier().bindingKeys(), "displayPropertyKeys");
}
}
public void setFetchSpecificationName(String value) {
d2wContext().takeValueForKey(value,"fetchSpecificationName");
//_fetchSpecificationName=name;
EOEntity e=entity();
setFetchSpecification(e.fetchSpecificationNamed(value));
}
public String fetchSpecificationName() {
return (String)d2wContext().valueForKey("fetchSpecificationName");
}
public EOFetchSpecification queryFetchSpecification() {
NSDictionary valuesFromBinding=displayGroup.queryMatch();
if(fetchSpecification() != null) {
return fetchSpecification().fetchSpecificationWithQualifierBindings(valuesFromBinding);
}
return null;
}
protected void pushQueryBindingsForName(String name) {
NSDictionary queryBindings = queryBindings();
if (queryBindings != null && (queryBindings instanceof NSMutableDictionary)) {
NSMutableDictionary mutableQueryBindings = (NSMutableDictionary) queryBindings;
NSDictionary source = (NSDictionary) NSKeyValueCoding.Utility.valueForKey(displayGroup, name);
mutableQueryBindings.setObjectForKey(source.mutableClone(), name);
}
}
@Override
public void takeValuesFromRequest(WORequest request, WOContext context) {
super.takeValuesFromRequest(request, context);
substituteValueForNullableQueryKeys();
saveQueryBindings();
}
@Override
public void appendToResponse(WOResponse response, WOContext context) {
loadQueryBindings();
super.appendToResponse(response, context);
if (ERXValueUtilities.booleanValueWithDefault(d2wContext().valueForKey("enableQueryForNullValues"), false)) {
ERXResponseRewriter.addScriptResourceInHead(response, context, "ERDirectToWeb", "ERD2WQueryPage.js");
}
}
protected void saveQueryBindings() {
NSDictionary queryBindings = queryBindings();
if (queryBindings != null) {
pushQueryBindingsForName("queryMin");
pushQueryBindingsForName("queryMax");
pushQueryBindingsForName("queryMatch");
pushQueryBindingsForName("queryOperator");
pushQueryBindingsForName("queryBindings");
}
}
protected void loadQueryBindings() {
if (!didLoadQueryBindings) {
NSDictionary queryBindings = queryBindings();
if (queryBindings != null) {
pullQueryBindingsForName("queryMin");
pullQueryBindingsForName("queryMax");
pullQueryBindingsForName("queryMatch");
pullQueryBindingsForName("queryOperator");
pullQueryBindingsForName("queryBindings");
didLoadQueryBindings = true;
}
}
}
@Override
public void awake() {
super.awake();
}
public boolean isDeep() {
return ERXValueUtilities.booleanValue(d2wContext().valueForKey("isDeep"));
}
public NSDictionary queryBindings() {
if (queryBindings == null) {
queryBindings = (NSDictionary) valueForBinding("queryBindings");
}
return queryBindings;
}
public void setQueryBindings(NSDictionary dictionary) {
queryBindings = dictionary;
}
public boolean usesDistinct() {
return ERXValueUtilities.booleanValue(d2wContext().valueForKey("usesDistinct"));
}
public boolean refreshRefetchedObjects() {
return ERXValueUtilities.booleanValue(d2wContext().valueForKey("refreshRefetchedObjects"));
}
public int fetchLimit() {
return ERXValueUtilities.intValueWithDefault(d2wContext().valueForKey("fetchLimit"), 0);
}
public NSArray prefetchingRelationshipKeyPaths(){
return ERXValueUtilities.arrayValue(d2wContext().valueForKey("prefetchingRelationshipKeyPaths"));
}
// add the ability to AND the existing qualifier from the DG
public EOQualifier qualifier() {
EOQualifier q = displayGroup.qualifier();
EOQualifier q2 = displayGroup.qualifierFromQueryValues();
return q == null ? q2 : (q2 == null ? q : new EOAndQualifier(new NSArray(new Object[] { q, q2 })));
}
protected Boolean showResults = null;
public boolean showResults() {
return Boolean.TRUE.equals(showResults);
}
public void setShowResults(boolean value) {
showResults = value;
}
public WOComponent queryAction() {
WOComponent nextPage = null;
// If we have a validation delegate, validate the query values before actually performing the query.
ERDQueryValidationDelegate queryValidationDelegate = queryValidationDelegate();
if (queryValidationDelegate != null) {
clearValidationFailed();
setErrorMessage(null);
try {
queryValidationDelegate.validateQuery(this);
} catch (NSValidation.ValidationException ex) {
setErrorMessage(ERXLocalizer.currentLocalizer().localizedTemplateStringForKeyWithObject("CouldNotQuery", ex));
validationFailedWithException(ex, null, "queryExceptionKey");
}
if (hasErrors()) {
return context().page();
}
}
if (ERXValueUtilities.booleanValue(d2wContext().valueForKey("showListInSamePage"))) {
setShowResults(true);
} else {
nextPage = nextPageFromDelegate();
if (nextPage == null) {
String listConfigurationName = (String) d2wContext().valueForKey("listConfigurationName");
ListPageInterface listpageinterface;
if (listConfigurationName != null) {
listpageinterface = (ListPageInterface) D2W.factory().pageForConfigurationNamed(listConfigurationName, session());
} else {
listpageinterface = D2W.factory().listPageForEntityNamed(entity().name(), session());
}
listpageinterface.setDataSource(queryDataSource());
listpageinterface.setNextPage(context().page());
nextPage = (WOComponent) listpageinterface;
}
}
return nextPage;
}
// returning a null query data source if cancel was clicked
private boolean _wasCancelled;
public WOComponent cancelAction() {
WOComponent result = null;
try {
_wasCancelled = true;
result = nextPageFromDelegate();
if (result == null) {
// CHECKME AK: or return null?? no way of knowing...
result = nextPage();
}
} finally {
_wasCancelled = false;
}
return result;
}
//CHECKME AK: this variable doesn't seem like such a good idea, in particular as there is no setter??
public WOComponent returnPage;
public WOComponent returnAction() {
return returnPage != null ? returnPage : nextPage();
}
@Override
public boolean showCancel() {
return nextPage() != null;
}
/**
* Assembles the data source for the search results page, configured for the current query. If a
* {@link #queryDataSourceDelegate()} is defined, the delegate's implementation is invoked. Otherwise,
* the {@link #defaultQueryDataSource()} is returned.
* @return the prepared data source
*/
public EODataSource queryDataSource() {
if (_wasCancelled) {
return null;
}
ERDQueryDataSourceDelegateInterface delegate = queryDataSourceDelegate();
if (delegate != null) {
return delegate.queryDataSource(this);
} else {
return defaultQueryDataSource();
}
}
/**
* Sets the query data source.
* @param datasource to be used as the query data source
*/
public void setQueryDataSource(EODataSource datasource) {
setDataSource(datasource);
}
/**
* Default implementation of which assembles the data source for the search results page, configured
* for the current query.
* @return the prepared data source
*/
public EODataSource defaultQueryDataSource() {
EODataSource ds = dataSource();
if (ds == null || !(ds instanceof EODatabaseDataSource)) {
ds = new EODatabaseDataSource(session().defaultEditingContext(), entity().name());
setDataSource(ds);
}
EOFetchSpecification fs = queryFetchSpecification();
if (fs == null) {
fs = ((EODatabaseDataSource) ds).fetchSpecification();
fs.setQualifier(qualifier());
fs.setIsDeep(isDeep());
fs.setUsesDistinct(usesDistinct());
fs.setRefreshesRefetchedObjects(refreshRefetchedObjects());
} else {
((EODatabaseDataSource) ds).setFetchSpecification(fs);
}
int limit = fetchLimit();
if (limit != 0)
fs.setFetchLimit(limit);
NSArray prefetchingRelationshipKeyPaths = prefetchingRelationshipKeyPaths();
if (prefetchingRelationshipKeyPaths != null && prefetchingRelationshipKeyPaths().count() > 0) {
fs.setPrefetchingRelationshipKeyPaths(prefetchingRelationshipKeyPaths);
}
return ds;
}
/**
* Gets the query data source delegate.
* @return the query data source delegate
*/
public ERDQueryDataSourceDelegateInterface queryDataSourceDelegate() {
if (queryDataSourceDelegate == null) {
queryDataSourceDelegate = (ERDQueryDataSourceDelegateInterface)d2wContext().valueForKey("queryDataSourceDelegate");
}
return queryDataSourceDelegate;
}
/**
* Sets the query data source delegate.
* @param delegate to use as the query data source delegate
*/
public void setQueryDataSourceDelegate(ERDQueryDataSourceDelegateInterface delegate) {
queryDataSourceDelegate = delegate;
}
/**
* <span class="en">
* Gets the query validation delegate.
*
* @return the query validation delegate
* </span>
*
* <span class="ja">
* クエリ検証デリゲートを戻します。
*
* @return クエリ検証デリゲート
* </span>
*/
public ERDQueryValidationDelegate queryValidationDelegate() {
if (null == queryValidationDelegate) {
queryValidationDelegate = (ERDQueryValidationDelegate)d2wContext().valueForKey("queryValidationDelegate");
}
return queryValidationDelegate;
}
/**
* <span class="en">
* Sets the query validation delegate.
*
* @param delegate to use as the query validation delegate
* </span>
*
* <span class="ja">
* クエリ検証デリゲートをセットします。
*
* @param delegate - クエリ検証デリゲート (@see ERDQueryValidationDelegate)
* </span>
*/
public void setQueryValidationDelegate(ERDQueryValidationDelegate delegate) {
queryValidationDelegate = delegate;
}
/**
* Gets the display group.
* @return the display group
*/
public WODisplayGroup displayGroup() {
return displayGroup;
}
public String headerTemplate() {
return fetchLimit() != 0 ? "ERD2WQueryPage.restrictedMessage" : "ERD2WQueryPage.plainMessage";
}
/**
* Set a search value for the display group query match. When the value is null is gets removed from the
* dict, when the operator is null and the value isn't, "=" is chosen.
* When operator is "<" is uses <code>queryMatch()</code>, if it is ">" is uses <code>queryMin()</code>,
* so you can use it with the various date range components.
* @param value to assign to the queryMatch dictionary for the given key
* @param operator used for comparing the value
* @param key to use
*/
public void setQueryMatchForKey(Object value, String operator, String key) {
NSMutableDictionary queryDict = displayGroup().queryMatch();
NSMutableDictionary operatorDict = displayGroup().queryOperator();
if(">".equals(operator)) {
queryDict = displayGroup().queryMin();
operatorDict = new NSMutableDictionary();
} else if ("<".equals(operator)) {
queryDict = displayGroup().queryMax();
operatorDict = new NSMutableDictionary();
}
if(value != null) {
queryDict.setObjectForKey(value, key);
if(operator != null) {
operatorDict.setObjectForKey(operator, key);
} else {
operatorDict.removeObjectForKey(key);
}
} else {
queryDict.removeObjectForKey(key);
operatorDict.removeObjectForKey(key);
}
}
public void setCancelDelegate(NextPageDelegate cancelDelegate) {
// FIXME not implemented!
}
/**
* Discovers the property keys that can be queried for a NULL value.
* @return the array of nullable and/or non-mandatory property keys
*/
public NSArray nullablePropertyKeys() {
if (null == _nullablePropertyKeys) {
NSMutableArray array = new NSMutableArray();
String preKey = propertyKey();
D2WContext d2wContext = d2wContext();
for (Enumeration keysEnum = displayPropertyKeys().objectEnumerator(); keysEnum.hasMoreElements();) {
String key = (String)keysEnum.nextElement();
setPropertyKey(key);
Object isMandatory = d2wContext.valueForKey("isMandatory");
if (isMandatory != null && !ERXValueUtilities.booleanValue(isMandatory)) {
array.addObject(key);
}
}
_nullablePropertyKeys = array;
setPropertyKey(preKey); // Restore the property key.
}
return _nullablePropertyKeys;
}
/**
* Determines if the null query checkbox for the current D2W property key should be checked.
* @return true if the checkbox should be checked
*/
public boolean isNullQueryCheckedForCurrentProperty() {
return Boolean.TRUE.equals(keysToQueryForNull.valueForKey(propertyKey()));
}
/**
* Sets the flag denoting a property key is being queried for a null value.
* @param value of the checkbox' checked attribute
*/
public void setIsNullQueryCheckedForCurrentProperty(boolean value) {
keysToQueryForNull.takeValueForKey(value, propertyKey());
}
/**
* Determines if the null query checkbox can be shown for the current D2W property key should be checked.
* @return true if the checkbox should be checked
*/
public boolean canQueryCurrentPropertyForNullValue() {
boolean enabled = ERXValueUtilities.booleanValueWithDefault(d2wContext().valueForKey("enableQueryForNullValues"), false);
boolean propertyAllowsQuery = ERXValueUtilities.booleanValueWithDefault(d2wContext().valueForKey("canQueryPropertyForNullValues"), true);
return (enabled && propertyAllowsQuery && nullablePropertyKeys().containsObject(propertyKey()));
}
/**
* When querying for properties with a null value, and the null value checkbox for a property key is checked, this
* method substitutes <code>NSKeyValueCoding.NullValue</code> into the display group's query dictionaries for that
* property key.
*/
protected void substituteValueForNullableQueryKeys() {
WODisplayGroup displayGroup = displayGroup();
for(Enumeration nullableKeysEnum = nullablePropertyKeys().objectEnumerator(); nullableKeysEnum.hasMoreElements();) {
String key = (String)nullableKeysEnum.nextElement();
Boolean value = (Boolean)keysToQueryForNull.objectForKey(key);
if (Boolean.TRUE.equals(value)) {
displayGroup.queryOperator().takeValueForKey(EOQualifier.stringForOperatorSelector(EOQualifier.QualifierOperatorEqual), key);
if (displayGroup.queryBindings().valueForKey(key) != null) {
displayGroup.queryBindings().takeValueForKey(NSKeyValueCoding.NullValue, key);
} else {
displayGroup.queryMatch().takeValueForKey(NSKeyValueCoding.NullValue, key);
}
}
}
}
}