/*
* 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.components;
import org.apache.log4j.Logger;
import com.webobjects.appserver.WOComponent;
import com.webobjects.appserver.WOContext;
import com.webobjects.appserver.WOResponse;
import com.webobjects.directtoweb.D2WContext;
import com.webobjects.directtoweb.D2WPage;
import com.webobjects.foundation.NSDictionary;
import er.directtoweb.ERDirectToWeb;
import er.directtoweb.pages.ERD2WPage;
import er.extensions.components.ERXNonSynchronizingComponent;
import er.extensions.eof.ERXConstant;
import er.extensions.validation.ERXExceptionHolder;
/**
* <div class="en">
* Base class of many custom components.
* <p>
* Has a lot of nifty features including resolving bindings against the rule system and inherits all the value pulling methods from {@link ERXNonSynchronizingComponent}.
* Subclasses should be able to run stand alone without a D2W context. This is achieved by pulling values first from the bindings, then from the d2wContext and finally from an "extraBindings" binding.
* </div>
*
* <div class="ja">
* たくさんのカスタム・コンポーネントのベース・クラスである
*
* ルール・システムへのバインディングや {@link ERXNonSynchronizingComponent} の値バインディング取得機能等の必要な処理をたくさん含みます。
*
* サブクラスは D2W コンテキスト無しでスタンドアロンで実行可能です。最初はコンポーネント・バインディングを優先で取得を試し、
* だめなら、 d2wContext と後は "extraBindings" バインディングより。
*
* @d2wKey localContext - d2wContext (deprecated)
* @d2wKey d2wContext - d2wContext
* @d2wKey key - プロパティ・キー
* @d2wKey extraBindings - オプション・バインディング
* @d2wKey propertyKey - プロパティ・キー
* </div>
*/
public abstract class ERDCustomComponent extends ERXNonSynchronizingComponent implements ERXExceptionHolder {
/**
* 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;
public static interface Keys {
public static final String key = "key";
public static final String localContext = "localContext";
public static final String d2wContext = "d2wContext";
public static final String extraBindings = "extraBindings";
public static final String propertyKey = "propertyKey";
}
/** logging support */
public final static Logger log = Logger.getLogger(ERDCustomComponent.class);
/** Designated constructor */
public ERDCustomComponent(WOContext context) {
super(context);
}
/** Holds the {@link D2WContext}. */
private D2WContext d2wContext;
/** Holds the current D2W task. */
private String task;
/** Holds the property key. */
private String key;
/** Holds the extra bindings. */
protected Object extraBindings;
//CHECKME ak: who needs this?
protected static final Integer TRUE = ERXConstant.OneInteger;
protected static final Integer FALSE = ERXConstant.ZeroInteger;
/** Sets the {@link D2WContext}. Applies when used inside a D2WCustomComponent.*/
public void setLocalContext(D2WContext value) {
setD2wContext(value);
}
/** Sets the {@link D2WContext}. Applies when used inside a property key repetition.*/
public void setD2wContext(D2WContext value) {
d2wContext = value;
}
/** The active {@link D2WContext}. Simply calls to {@link #d2wContext()}*/
public D2WContext localContext() {
return d2wContext();
}
/** The active {@link D2WContext}.*/
public D2WContext d2wContext() {
return d2wContextFromBindings();
}
/**
* <span class="en">
* Returns the active d2wContext. If the value was not set via KVC, tries to get the value from the bindings if the component is non-syncing
* </span>
*
* <span class="ja">
* アクティブな d2wContext を戻します。
* KVC で設定されていなければ、非シンクロナイズ・コンポーネントのバインディングより取得を試し見る
* </span>
*/
protected D2WContext d2wContextFromBindings() {
if (d2wContext == null && !synchronizesVariablesWithBindings()) {
d2wContext = (D2WContext)super.valueForBinding(Keys.localContext);
if(d2wContext == null) {
d2wContext = (D2WContext)super.valueForBinding(Keys.d2wContext);
}
}
return d2wContext;
}
/**
* Gets the current D2W task.
*/
public String task() {
if (task == null) {
task = (String)valueForBinding("task");
}
return task;
}
public boolean taskIsEdit() { return "edit".equals(task()); }
public boolean taskIsInspect() { return "inspect".equals(task()); }
public boolean taskIsList() { return "list".equals(task()); }
/**
* <span class="en">Validation Support. Passes errors to the parent. </span>
* <span class="ja">Validation Support. エラーを親コンポーネントに渡す</span>
*/
@Override
public void validationFailedWithException (Throwable e, Object value, String keyPath) {
parent().validationFailedWithException(e,value,keyPath);
}
/**
* <span class="en">Implementation of the {@link ERXExceptionHolder} interface. Clears exceptions in the parent if possible.</span>
* <span class="ja">{@link ERXExceptionHolder} インタフェース実装。可能であれば、親のエラーをクリアします。</span>
*/
public void clearValidationFailed() {
// Since this component can be used stand alone, we might not necessarily
// have an exception holder as our parent --> testing
if (parent() instanceof ERXExceptionHolder)
((ERXExceptionHolder)parent()).clearValidationFailed();
}
// CHECKME ak who needs this?
public Integer integerBooleanForBinding(String binding) {
return booleanValueForBinding(binding) ? ERDCustomComponent.TRUE : ERDCustomComponent.FALSE;
}
/**
* <span class="en">
* Checks if the binding can be pulled. If the component is synching, throws an Exception. Otherwise checks the superclass and if the value for the binding is not null.
* </span>
*
* <span class="ja">
* バインディングが取得可能かどうかをチェックします。シンクロナイズ・コンポーネントの場合にはエラーを発行します。
* そうでなければ、スーパークラスをバインディングが非 null であるようにチェックします
* </span>
*/
@Override
public boolean hasBinding(String binding) {
// FIXME: Turn this check off in production
if (synchronizesVariablesWithBindings()) {
throw new IllegalStateException("HasBinding being used in an object of class " + getClass().getName() + " that synchronizesVariablesWithBindings == true");
}
return (super.hasBinding(binding) || valueForBinding(binding) != null);
}
/** Utility to dump some debug info about this component and its parent */
protected void logDebugInfo() {
if (log.isDebugEnabled()) {
log.debug("***** ERDCustomComponent: this: " + getClass().getName());
log.debug("***** ERDCustomComponent: parent(): + (" + ((parent() == null) ? "null" : parent().getClass().getName()) + ")");
log.debug(" " + parent());
log.debug("***** ERDCustomComponent: parent() instanceof ERDCustomComponent == " + (parent() instanceof ERDCustomComponent));
log.debug("***** ERDCustomComponent: parent() instanceof D2WCustomComponentWithArgs == " + (parent() instanceof ERD2WCustomComponentWithArgs));
log.debug("***** ERDCustomComponent: parent() instanceof D2WStatelessCustomComponentWithArgs == " + (parent() instanceof ERD2WStatelessCustomComponentWithArgs));
log.debug("***** ERDCustomComponent: parent() instanceof D2WCustomQueryComponentWithArgs == " + (parent() instanceof ERDCustomQueryComponentWithArgs));
}
}
/**
* <span class="en">Utility to pull the value from the components parent, if the parent is a D2W wrapper component.</span>
* <span class="ja">親コンポーネントが D2W ラパー・コンポーネントの場合のバインディング取得ユーティリティ</span>
*/
protected Object parentValueForBinding(String binding) {
WOComponent parent = parent();
if (parent instanceof ERDCustomComponent ||
parent instanceof ERD2WCustomComponentWithArgs ||
parent instanceof ERD2WStatelessCustomComponentWithArgs) {
log.debug("inside the parent instanceof branch");
// this will eventually bubble up to a D2WCustomComponentWithArgs, where it will (depending on the actual binding)
// go to the d2wContext
return parent.valueForBinding(binding);
}
return null;
}
/**
* <span class="en">Utility to pull the value from the components actual bindings. </span>
* <span class="ja">コンポーネントのバインディングから取得するユーティリティ</span>
*/
protected Object originalValueForBinding(String binding) {
return super.valueForBinding(binding);
}
/**
* <span class="en">Utility to pull the value from the {@link D2WContext}. </span>
* <span class="ja">{@link D2WContext} から値を取得するユーティリティ</span>
*/
protected Object d2wContextValueForBinding(String binding) {
return d2wContextFromBindings().valueForKey(binding);
}
/** Utility to pull the value from the extra bindings if supplied. */
protected Object extraBindingsValueForBinding(String binding) {
if(extraBindings() instanceof NSDictionary)
return ((NSDictionary)extraBindings()).objectForKey(binding);
return null;
}
/**
* <span class="en">
* Fetches an object from the bindings.
* Tries the actual supplied bindings, the supplied d2wContext, the parent and finally the extra bindings dictionary.
* </span>
*
* <span class="ja">
* バインディングを使って、オブジェクトをフェッチする
* バインディングを次の順で取得を試す:コンポーネント、d2wContext、親コンポーネント、オプション・バインディング・ディクショナリー
* </span>
*/
@Override
public Object valueForBinding(String binding) {
Object value=null;
logDebugInfo();
if (super.hasBinding(binding)) {
if (log.isDebugEnabled())
log.debug("super.hasBinding(binding) == true for binding "+binding);
value = originalValueForBinding(binding);
} else if(d2wContextFromBindings() != null) {
if (log.isDebugEnabled())
log.debug("has d2wContext == true for binding "+binding);
value = d2wContextValueForBinding(binding);
} else {
value = parentValueForBinding(binding);
}
if (value == null && binding != null && extraBindings() != null) {
if (log.isDebugEnabled())
log.debug("inside the extraBindings branch for binding "+binding);
value = extraBindingsValueForBinding(binding);
}
if (log.isDebugEnabled()) {
if (value != null)
log.debug("returning " + value.getClass().getName() + ": " + value+" for binding "+binding);
else
log.debug("returning value: null for binding "+binding);
}
return value;
}
/** Used by stateful but non-synching subclasses */
@Override
public void resetCachedBindingsInStatefulComponent() {
super.resetCachedBindingsInStatefulComponent();
extraBindings = null;
key = null;
d2wContext = null;
task = null;
}
/** Used by stateless subclasses. */
@Override
public void reset() {
super.reset();
extraBindings = null;
key = null;
d2wContext = null;
task = null;
}
/** Sets the extra bindings. */
public void setExtraBindings(Object value) { extraBindings = value; }
/** Extra bindings supplied to the component. If this is a dictionary, it will be used for additional bindings.*/
public Object extraBindings() {
if (extraBindings == null && !synchronizesVariablesWithBindings())
extraBindings = super.valueForBinding(Keys.extraBindings);
return extraBindings;
}
/** Sets the property key. */
public void setKey(String newKey) { key=newKey; }
/** The active property key. */
public String key() {
//FIXME : バグフィックス:複数のカスタム・コンポーネントを複数のタブに設定すると正しく設定されず。
//if(synchronizesVariablesWithBindings()) {
// key = null;
//}
if(!synchronizesVariablesWithBindings()) {
if (key==null) {
key=(String)super.valueForBinding(Keys.key);
}
}
if (key==null && d2wContext() != null) {
key=(String)d2wContext().valueForKey(Keys.propertyKey);
}
return key;
}
/** Overridden from superclass to turn on component synching, which is the default. */
@Override
public boolean synchronizesVariablesWithBindings() { return true; } // CHECKME why then does this class subclass ERXNonSynchronizingComponent?
/** Is D2W debugging enabled. */
public boolean d2wDebuggingEnabled() {
return ERDirectToWeb.d2wDebuggingEnabled(session());
}
/**
* <span class="en">Should the component name be shown. </span>
* <span class="ja">コンポーネント名を表示する?</span>
*/
public boolean d2wComponentNameDebuggingEnabled() {
return ERDirectToWeb.d2wComponentNameDebuggingEnabled(session());
}
/**
* <span class="en">Should the property keys be shown.</span>
* <span class="ja">プロパティ・キーを表示する?</span>
*/
public boolean d2wPropertyKeyDebuggingEnabled() {
return ERDirectToWeb.d2wPropertyKeyDebuggingEnabled(session());
}
/**
* <span class="en">
* Finds the containing D2WPage, if possible. There are certain situations when having a
* reference to the containing D2W page is useful, e.g., when needing to use the userInfo
* dictionary of {@link ERD2WPage} to pass information between subcomponents.
*
* @return the containing D2WPage
* </span>
*
* <span class="ja">
* 可能であれば、D2WPage の親コンポーネントを戻します。
* 場合によって、親 D2W ページへのアクセスは必要です。
* ユーザ情報ディクショナリーへのアクセス等でサブコンポーネントの情報交換が可能です。
*
* @return 親 D2WPage
* </span>
*/
public D2WPage d2wPage() {
// Can't just use context().page(), because the d2wPage isn't necessarily the top-level
// component.
WOComponent component = this;
do {
component = component.parent();
} while( component != null && !(component instanceof D2WPage) );
return (D2WPage)component;
}
@Override
public void appendToResponse(WOResponse r, WOContext c) {
if(!ERDirectToWeb.shouldRaiseException(false)) {
// in the case where we are non-synchronizing but not stateless, make sure we pull again
if (!synchronizesVariablesWithBindings() && !isStateless()) {
reset();
}
super.appendToResponse(r,c);
} else {
try {
// in the case where we are non-synchronizing but not stateless, make sure we pull again
if (!synchronizesVariablesWithBindings() && !isStateless()) {
reset();
}
super.appendToResponse(r,c);
} catch(Exception ex) {
ERDirectToWeb.reportException(ex, d2wContext());
}
}
}
}