/*
* 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.extensions;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.log4j.Logger;
import com.webobjects.appserver.WOApplication;
import com.webobjects.appserver.WOSession;
import com.webobjects.eoaccess.EOAttribute;
import com.webobjects.eoaccess.EODatabase;
import com.webobjects.eoaccess.EODatabaseContext;
import com.webobjects.eoaccess.EOEntity;
import com.webobjects.eoaccess.EOModelGroup;
import com.webobjects.eoaccess.EOQualifierSQLGeneration;
import com.webobjects.eoaccess.EOQualifierSQLGeneration.Support;
import com.webobjects.eoaccess.EORelationship;
import com.webobjects.eoaccess.EOSQLExpression;
import com.webobjects.eoaccess.EOUtilities;
import com.webobjects.eoaccess.ERXEntityDependencyOrderingDelegate;
import com.webobjects.eoaccess.ERXModel;
import com.webobjects.eocontrol.EOEnterpriseObject;
import com.webobjects.eocontrol.EOFetchSpecification;
import com.webobjects.eocontrol.EOKeyValueQualifier;
import com.webobjects.eocontrol.EOQualifier;
import com.webobjects.eocontrol.EOQualifierVariable;
import com.webobjects.eocontrol.EOSharedEditingContext;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSBundle;
import com.webobjects.foundation.NSDictionary;
import com.webobjects.foundation.NSForwardException;
import com.webobjects.foundation.NSKeyValueCoding;
import com.webobjects.foundation.NSLog;
import com.webobjects.foundation.NSMutableArray;
import com.webobjects.foundation.NSMutableDictionary;
import com.webobjects.foundation.NSNotification;
import com.webobjects.foundation.NSNotificationCenter;
import com.webobjects.foundation.NSSelector;
import com.webobjects.foundation._NSStringUtilities;
import com.webobjects.jdbcadaptor.JDBCAdaptorException;
import er.extensions.appserver.ERXApplication;
import er.extensions.eof.ERXAdaptorChannelDelegate;
import er.extensions.eof.ERXConstant;
import er.extensions.eof.ERXDatabase;
import er.extensions.eof.ERXDatabaseContext;
import er.extensions.eof.ERXDatabaseContextDelegate;
import er.extensions.eof.ERXDatabaseContextMulticastingDelegate;
import er.extensions.eof.ERXEC;
import er.extensions.eof.ERXEOAccessUtilities;
import er.extensions.eof.ERXEnterpriseObjectCache;
import er.extensions.eof.ERXEntityClassDescription;
import er.extensions.eof.ERXGenericRecord;
import er.extensions.eof.ERXModelGroup;
import er.extensions.eof.ERXObjectStoreCoordinatorPool;
import er.extensions.eof.ERXSharedEOLoader;
import er.extensions.eof.qualifiers.ERXFullTextQualifier;
import er.extensions.eof.qualifiers.ERXFullTextQualifierSupport;
import er.extensions.eof.qualifiers.ERXPrimaryKeyListQualifier;
import er.extensions.eof.qualifiers.ERXRegExQualifier;
import er.extensions.eof.qualifiers.ERXToManyQualifier;
import er.extensions.foundation.ERXArrayUtilities;
import er.extensions.foundation.ERXConfigurationManager;
import er.extensions.foundation.ERXMutableURL;
import er.extensions.foundation.ERXPatcher;
import er.extensions.foundation.ERXProperties;
import er.extensions.foundation.ERXRuntimeUtilities;
import er.extensions.foundation.ERXStringUtilities;
import er.extensions.foundation.ERXSystem;
import er.extensions.foundation.ERXValueUtilities;
import er.extensions.jdbc.ERXJDBCAdaptor;
import er.extensions.localization.ERXLocalizer;
import er.extensions.logging.ERXLogger;
import er.extensions.partials.ERXPartialInitializer;
import er.extensions.qualifiers.ERXFalseQualifier;
import er.extensions.qualifiers.ERXFalseQualifierSupport;
import er.extensions.qualifiers.ERXTrueQualifier;
import er.extensions.qualifiers.ERXTrueQualifierSupport;
import er.extensions.remoteSynchronizer.ERXRemoteSynchronizer;
import er.extensions.validation.ERXValidationFactory;
/**
* Principal class of the ERExtensions framework. This class
* will be loaded at runtime when the ERExtensions bundle is
* loaded (even before the Application constructor is called)
* This class has a boat-load of stuff in it that will hopefully
* be finding better homes in the future. This class serves as
* the initialization point of this framework, look in the static
* initializer to see all the stuff that is initially setup when
* this class is loaded. This class also has a boat load of
* string, array and EOF utilities as well as the factory methods
* for creating editing contexts with the default delegates set.
*/
public class ERXExtensions extends ERXFrameworkPrincipal {
/** Notification name, posted before object will change in an editing context */
public final static String objectsWillChangeInEditingContext= "ObjectsWillChangeInEditingContext";
/** Notification name, posted before EOAdaptor debug logging will change its setting. */
public final static String eoAdaptorLoggingWillChangeNotification = "EOAdaptorLoggingWillChange";
/** logging support */
private static Logger _log;
private static boolean _initialized;
public ERXExtensions() {
}
/** holds the default model group */
protected volatile ERXModelGroup defaultModelGroup;
/**
* Delegate method for the {@link EOModelGroup} class delegate.
* @return a fixed ERXModelGroup
*/
public EOModelGroup defaultModelGroup() {
if(defaultModelGroup == null) {
synchronized (ERXModel._ERXGlobalModelLock) {
if (defaultModelGroup == null) {
String defaultModelGroupClassName = ERXProperties.stringForKey("er.extensions.defaultModelGroupClassName");
if (defaultModelGroupClassName == null) {
defaultModelGroup = new ERXModelGroup();
}
else {
try {
defaultModelGroup = Class.forName(defaultModelGroupClassName).asSubclass(ERXModelGroup.class).newInstance();
}
catch (Exception e) {
throw new RuntimeException("Failed to create custom ERXModelGroup subclass '" + defaultModelGroupClassName + "'.", e);
}
}
defaultModelGroup.loadModelsFromLoadedBundles();
}
}
}
return defaultModelGroup;
}
/**
* Configures the framework. All the bits and pieces that need
* to be configured are configured, those that need to happen
* later are delayed by registering an observer for notifications
* that are posted when the application is finished launching.
* This public observer is used to perform basic functions in
* response to notifications. Specifically it handles
* configuring the adaptor context so that SQL debugging can
* be enabled and disabled on the fly through the log4j system.
* Handling cleanup issues when sessions timeout, i.e. releasing
* all references to editing contexts created for that session.
* Handling call all of the <code>did*</code> methods on
* {@link ERXGenericRecord} subclasses after an editing context
* has been saved. This delegate is also responsible for configuring
* {@link ERXValidationFactory}.
* This delegate is configured when this framework is loaded.
*/
@Override
protected void initialize() {
NSNotificationCenter.defaultCenter().addObserver(this,
new NSSelector("bundleDidLoad", ERXConstant.NotificationClassArray),
ERXApplication.AllBundlesLoadedNotification,
null);
}
public void bundleDidLoad(NSNotification n) {
if(_initialized) return;
_initialized = true;
try {
// This will load any optional configuration files,
// ensures that WOOutputPath's was processed with this @@
// variable substitution. WOApplication uses WOOutputPath in
// its constructor so we need to modify it before calling
// the constructor.
ERXConfigurationManager.defaultManager().initialize();
EOModelGroup.setClassDelegate(this);
ERXSystem.updateProperties();
// AK: enable this when we're ready
// WOEncodingDetector.sharedInstance().setFallbackEncoding(CharEncoding.UTF_8);
// GN: configure logging with optional custom subclass of ERXLogger
String className = ERXProperties.stringForKey("er.extensions.erxloggerclass");
if (className != null) {
Class loggerClass = Class.forName(className);
Method method = loggerClass.getDeclaredMethod(ERXLogger.CONFIGURE_LOGGING_WITH_SYSTEM_PROPERTIES, (Class[]) null);
method.invoke(loggerClass, (Object[]) null);
}
else {
// default behaviour:
ERXLogger.configureLoggingWithSystemProperties();
}
ERXArrayUtilities.initialize();
// False by default
if (ERXValueUtilities.booleanValue(System.getProperty(ERXSharedEOLoader.PatchSharedEOLoadingPropertyKey))) {
ERXSharedEOLoader.patchSharedEOLoading();
}
ERXExtensions.configureAdaptorContextRapidTurnAround(this);
ERXJDBCAdaptor.registerJDBCAdaptor();
if (EODatabaseContext.defaultDelegate() == null) {
if (ERXProperties.booleanForKey(ERXEntityDependencyOrderingDelegate.ERXEntityDependencyOrderingDelegateActiveKey)) {
ERXDatabaseContextMulticastingDelegate.addDefaultDelegate(new ERXEntityDependencyOrderingDelegate());
ERXDatabaseContextMulticastingDelegate.addDefaultDelegate(ERXDatabaseContextDelegate.defaultDelegate());
}
else {
EODatabaseContext.setDefaultDelegate(ERXDatabaseContextDelegate.defaultDelegate());
}
}
ERXAdaptorChannelDelegate.setupDelegate();
NSNotificationCenter.defaultCenter().addObserver(this, new NSSelector("sharedEditingContextWasInitialized", ERXConstant.NotificationClassArray), EOSharedEditingContext.DefaultSharedEditingContextWasInitializedNotification, null);
ERXEntityClassDescription.registerDescription();
ERXPartialInitializer.registerModelGroupListener();
} catch (Exception e) {
throw NSForwardException._runtimeExceptionForThrowable(e);
}
}
/**
* This method is called when the application has finished
* launching. Here is where log4j is configured for rapid
* turn around and the validation template system is configured.
*/
@Override
public void finishInitialization() {
ERXJDBCAdaptor.registerJDBCAdaptor();
// AK: we now setup the properties three times. At startup, in ERX.init
// and here. Note that this sucks beyond belief, as this will produce
// unforeseen results in several cases, but it's the only way to set up
// all parts of the property handling. The first install only loads plain
// and user props the second has no good way to set up the main bundle and this one
// comes too late for static inits
ERXConfigurationManager.defaultManager().loadConfiguration();
ERXProperties.populateSystemProperties();
ERXConfigurationManager.defaultManager().configureRapidTurnAround();
ERXLocalizer.initialize();
ERXValidationFactory.defaultFactory().configureFactory();
// update configuration with system properties that might depend
// on others like
// log4j.appender.SQL.File=@@loggingBasePath@@/@@port@@.sql
// loggingBasePath=/var/log/@@name@@
// name and port are resolved via WOApplication.application()
// ERXLogger.configureLoggingWithSystemProperties();
_log = Logger.getLogger(ERXExtensions.class);
ERXProperties.pathsForUserAndBundleProperties(true);
try {
// MS: initialize these with Class.forName(Whatever.class.getName()) because the .class class literal does not trigger static initializers to run in 1.5,
// which means that the sql generation support classes are not registered
registerSQLSupportForSelector(new NSSelector(ERXPrimaryKeyListQualifier.IsContainedInArraySelectorName), EOQualifierSQLGeneration.Support.supportForClass(Class.forName(ERXPrimaryKeyListQualifier.class.getName())));
registerSQLSupportForSelector(new NSSelector(ERXToManyQualifier.MatchesAllInArraySelectorName), EOQualifierSQLGeneration.Support.supportForClass(Class.forName(ERXToManyQualifier.class.getName())));
registerSQLSupportForSelector(new NSSelector(ERXRegExQualifier.MatchesSelectorName), EOQualifierSQLGeneration.Support.supportForClass(Class.forName(ERXRegExQualifier.class.getName())));
registerSQLSupportForSelector(new NSSelector(ERXFullTextQualifier.FullTextContainsSelectorName), EOQualifierSQLGeneration.Support.supportForClass(Class.forName(ERXFullTextQualifier.class.getName())));
}
catch (Throwable t) {
throw NSForwardException._runtimeExceptionForThrowable(t);
}
EOQualifierSQLGeneration.Support.setSupportForClass(new ERXFullTextQualifierSupport(), ERXFullTextQualifier.class);
EOQualifierSQLGeneration.Support.setSupportForClass(new ERXFalseQualifierSupport(), ERXFalseQualifier.class);
EOQualifierSQLGeneration.Support.setSupportForClass(new ERXTrueQualifierSupport(), ERXTrueQualifier.class);
// ERXObjectStoreCoordinatorPool has a static initializer, so just load the class if
// the configuration setting exists
if (ERXRemoteSynchronizer.remoteSynchronizerEnabled() || ERXProperties.booleanForKey("er.extensions.ERXDatabaseContext.activate")) {
String dbCtxClassName = ERXProperties.stringForKeyWithDefault("er.extensions.ERXDatabaseContext.className", ERXDatabaseContext.class.getName());
Class dbCtxClass = ERXPatcher.classForName(dbCtxClassName);
if(dbCtxClass == null) {
throw new IllegalStateException("er.extensions.ERXDatabaseContext.className not found: " + dbCtxClassName);
}
EODatabaseContext.setContextClassToRegister(dbCtxClass);
String dbClassName = ERXProperties.stringForKeyWithDefault("er.extensions.ERXDatabase.className", ERXDatabase.class.getName());
Class dbClass = ERXPatcher.classForName(dbClassName);
if(dbClass == null) {
throw new IllegalStateException("er.extensions.ERXDatabase.className not found: " + dbClassName);
}
if( ERXDatabase.class.isAssignableFrom( dbClass ) ) {
ERXDatabaseContext.setDatabaseContextClass( dbClass );
} else {
throw new IllegalStateException("er.extensions.ERXDatabase.className is not a subclass of ERXDatabase: " + dbClassName);
}
int mapCapacity = ERXProperties.intForKey( "er.extensions.ERXDatabase.snapshotCacheMapInitialCapacity" );
if( mapCapacity > 0 ) {
ERXDatabase.setSnapshotCacheMapInitialCapacity( mapCapacity );
}
float mapLoadFactor = ERXProperties.floatForKey( "er.extensions.ERXDatabase.snapshotCacheMapInitialLoadFactor" );
if( mapLoadFactor > 0.0f ) {
ERXDatabase.setSnapshotCacheMapInitialLoadFactor( mapLoadFactor );
}
}
ERXObjectStoreCoordinatorPool.initializeIfNecessary();
}
@Override
public void didFinishInitialization() {
ERXEnterpriseObjectCache.setApplicationDidFinishInitialization(true);
super.didFinishInitialization();
}
private static Map<String, Support> _qualifierKeys;
public static synchronized void registerSQLSupportForSelector(NSSelector selector, EOQualifierSQLGeneration.Support support) {
if(_qualifierKeys == null) {
_qualifierKeys = new HashMap<>();
EOQualifierSQLGeneration.Support old = EOQualifierSQLGeneration.Support.supportForClass(EOKeyValueQualifier.class);
EOQualifierSQLGeneration.Support.setSupportForClass(new KeyValueQualifierSQLGenerationSupport(old), EOKeyValueQualifier.class);
}
_qualifierKeys.put(selector.name(), support);
}
/**
* Support class that listens for EOKeyValueQualifiers that have a selector
* that was registered and uses their support instead.
* You'll use this mainly to bind queryOperators in display groups.
* @author ak
*
* Added SQL generation fix for EOKeyValueQualifiers in an edge case
* where the key is a key path (with two or more keys) and the last key
* is a derived attribute. For example, "customer.fullName" where the
* last key fullName is defined as: 'firstName || ' ' || lastName.
*
* @author Ricardo Parada
*/
public static class KeyValueQualifierSQLGenerationSupport extends EOQualifierSQLGeneration.Support {
public static final String HANDLES_KEY_PATH_WITH_DERIVED_ATTRIBUTE_PROPERTY_NAME = "er.extensions.KeyValueQualifierSQLGenerationSupport.handlesKeyPathWithDerivedAttribute";
private EOQualifierSQLGeneration.Support _old;
public KeyValueQualifierSQLGenerationSupport(EOQualifierSQLGeneration.Support old) {
_old = old;
}
private EOQualifierSQLGeneration.Support supportForQualifier(EOQualifier qualifier) {
EOQualifierSQLGeneration.Support support = null;
if(qualifier instanceof EOKeyValueQualifier) {
synchronized (_qualifierKeys) {
support = _qualifierKeys.get(((EOKeyValueQualifier)qualifier).selector().name());
}
}
if(support == null) {
support = _old;
}
return support;
}
@Override
public String sqlStringForSQLExpression(EOQualifier eoqualifier, EOSQLExpression e) {
// Check to see if checking for edge case is enabled
boolean handlesKeyPathWithDerivedAttribute = ERXProperties.booleanForKeyWithDefault(HANDLES_KEY_PATH_WITH_DERIVED_ATTRIBUTE_PROPERTY_NAME, true);
try {
if (handlesKeyPathWithDerivedAttribute && isKeyPathWithDerivedAttributeCase(e.entity(), eoqualifier)) {
return sqlStringForSQLExpressionWithKeyPathWithDerivedAttribute(eoqualifier, e);
}
// Otherwise handle normally
return supportForQualifier(eoqualifier).sqlStringForSQLExpression(eoqualifier, e);
}
catch (JDBCAdaptorException ex) {
ERXExtensions._log.error("Failed to generate sql string for qualifier " + eoqualifier + " on entity " + e.entity() + ".");
if (handlesKeyPathWithDerivedAttribute == false && isKeyPathWithDerivedAttributeCase(e.entity(), eoqualifier)) {
ERXExtensions._log.error("Consider setting " + HANDLES_KEY_PATH_WITH_DERIVED_ATTRIBUTE_PROPERTY_NAME + "=true");
}
throw ex;
}
}
/**
* This method handles an edge case where the key of the qualifier is a key path and
* the last key in the key path references a derived attribute. For example, if the
* key is "order.customerAge" and customerAge were defined as:
*
* <pre><code>(TRUNC(MONTHS_BETWEEN(orderDate,customer.birthDate)/12,0))</code></pre>
*
* and the key value qualifier was something like "order.customerAge > 50" then this
* method would generate SQL like this:
*
* <pre><code>(TRUNC(MONTHS_BETWEEN(T1.ORDER_DATE,T2.BIRTH_DATE)/12,0)) > 50</code></pre>
*
* Without this fix EOF ends up throwing an exception.
*
* @param eoqualifier An EOKeyValueQualifier with a key that is a key path and the last
* component in the key path is a derived attribute.
* @param e The EOSQLExpression participating in the SQL generation.
* @return The SQL for the eoqualifier.
*/
public String sqlStringForSQLExpressionWithKeyPathWithDerivedAttribute(EOQualifier eoqualifier, EOSQLExpression e) {
// Using the example where the key-value qualifier's key is the key path
// 'order.customerAge' and its last key 'customerAge' is an attribute defined
// as '(TRUNC(MONTHS_BETWEEN(orderDate,customer.birthDate)/12,0))'
// then we get the destination attribute, i.e. 'customerAge'. Then we parse
// the properties referenced by its definition, i.e. 'orderDate'
// and 'customer.birthDate'.
EOKeyValueQualifier keyValueQualifier = (EOKeyValueQualifier) eoqualifier;
String keyPath = keyValueQualifier.key();
EOAttribute derivedAttribute = destinationAttribute(e.entity(), keyPath);
NSArray<String> propertyKeys = parseDefinitionPropertyKeys(derivedAttribute);
// Get the keys preceding the derived attribute, for example, if key
// is 'order.customerAge' then the key preceding 'customerAge' would be
// 'order' which will become the prefix.
NSArray<String> allKeys = NSArray.componentsSeparatedByString(keyPath, ".");
String lastKey = allKeys.lastObject();
NSMutableArray<String> prefixKeys = allKeys.mutableClone();
prefixKeys.removeObject(lastKey);
String prefix = prefixKeys.componentsJoinedByString(".") + ".";
// Now we prefix every key path referenced by the definition and then
// generate SQL for them. For example the orderDate property key will
// become order.orderDate and customer.birthDate will become
// order.customer.birthDate. We then convert it to SQL, i.e. T2.ORDER_DATE
// and replace it in the definition. The end result is to have SQL that
// looks like this:
//
// (TRUNC(MONTHS_BETWEEN(T1.ORDER_DATE,T2.BIRTH_DATE)/12,0)) > 60.0
//
String sqlDefinition = derivedAttribute.definition();
for (String unPrefixedKey : propertyKeys) {
String prefixedKey = prefix + unPrefixedKey;
String sqlString = e.sqlStringForAttributeNamed(prefixedKey);
sqlDefinition = sqlDefinition.replaceAll(unPrefixedKey, sqlString);
}
// Up to this point we have generated the SQL for the key by replacing
// it for the SQL for its definition and referenced properties. Now
// we need to add the SQL for the selector and value in the qualifier.
// Make sure that we have a value before we proceed. If the value
// turns out to be an EOQualifierValue which is a place holder for
// a value then we don't have a value and we should throw an exception.
NSSelector qualifierSelector = keyValueQualifier.selector();
Object qualifierValue = keyValueQualifier.value();
if ((qualifierValue instanceof EOQualifierVariable)) {
throw new IllegalStateException(
"sqlStringForKeyValueQualifier: attempt to generate SQL for "
+ eoqualifier.getClass().getName()
+ " " + eoqualifier
+ " failed because the qualifier variable '$"
+ ((EOQualifierVariable)qualifierValue).key()
+ "' is unbound."
);
}
String keyString = sqlDefinition;
boolean isLike =
qualifierSelector.equals(EOQualifier.QualifierOperatorLike)
|| qualifierSelector.equals(EOQualifier.QualifierOperatorCaseInsensitiveLike);
Object value;
if (isLike) {
// Convert the special literal used in like expressions, i.e. * and ?
// into their SQL equivalent % and _
value = e.sqlPatternFromShellPattern((String)qualifierValue);
} else {
value = qualifierValue;
}
String qualifierSQL;
if (qualifierSelector.equals(EOQualifier.QualifierOperatorCaseInsensitiveLike)) {
String valueString = sqlStringForAttributeValue(e, derivedAttribute, value);
qualifierSQL = e.sqlStringForCaseInsensitiveLike(valueString, keyString);
} else {
String valueString = sqlStringForAttributeValue(e, derivedAttribute, value);
String operatorString = e.sqlStringForSelector(qualifierSelector, value);
qualifierSQL = _NSStringUtilities.concat(keyString, " ", operatorString, " ", valueString);
}
if (isLike) {
char escapeChar = e.sqlEscapeChar();
if (escapeChar != 0) {
qualifierSQL = _NSStringUtilities.concat(qualifierSQL, " ESCAPE '" + escapeChar + "'");
}
}
// If debug mode print something like this:
//
// KeyValueQualifierSQLGenerationSupport handled edge case for key-value qualifier with key referencing derived attribute: Order.customerAge
// Key value qualifier: (order.customerAge > 30)
// Key path: order.customerAge
// Attribute customerAge is defined as: TRUNC(MONTHS_BETWEEN(orderDate,customer.birthDate)/12,0)
// SQL generated: TRUNC(MONTHS_BETWEEN(T1.ORDER_DATE,T2.BIRTH_DATE)/12,0) > ?
//
if (ERXExtensions._log.isDebugEnabled()) {
ERXExtensions._log.debug(getClass().getSimpleName()
+ " handled edge case for key-value qualifier with key path"
+ " referencing a derived attribute: "
+ derivedAttribute.entity().name()
+ "."
+ derivedAttribute.name()
);
ERXExtensions._log.debug(" Key value qualifier: "+ keyValueQualifier);
ERXExtensions._log.debug(" Key path: " + keyValueQualifier.key());
ERXExtensions._log.debug(" Attribute " + derivedAttribute.name() + " is defined as: " + derivedAttribute.definition());
ERXExtensions._log.debug(" SQL generated: " + qualifierSQL);
}
return qualifierSQL;
}
/**
* Uses the EOSQLExpression provided to get the SQL string for value and
* corresponding attribute.
*
* @param e The EOSQLExpression to use to generate the SQL
* @param att The attribute corresponding to the value passed in
* @param value The value to convert to SQL
* @return The SQL string for the value
*/
public String sqlStringForAttributeValue(EOSQLExpression e, EOAttribute att, Object value) {
if (value != NSKeyValueCoding.NullValue
&& (((e.useBindVariables()) && (e.shouldUseBindVariableForAttribute(att))) || (e.mustUseBindVariableForAttribute(att)))) {
NSMutableDictionary<String, Object> binding = e.bindVariableDictionaryForAttribute(att, value);
e.addBindVariableDictionary(binding);
return (String)binding.objectForKey("BindVariablePlaceholder");
}
return e.formatValueForAttribute(value, att);
}
public static String formatValueForAttribute(EOSQLExpression e, Object value, EOAttribute attribute) {
return e.formatValueForAttribute(value, attribute);
}
/**
* Normally EOF can handle key value qualifiers with a key corresponding to a
* derived attribute, i.e. fullName attribute defined as firstName || ' ' || lastName.
* However, if the key is a key path, i.e. customer.fullName then EOF throws an
* exception. This method checks to see if the key in the eoqualifier is a key
* path and the last key in the key path corresponds to a derived attribute.
*
* @param entity The entity where the eoqualifier is rooted
* @param eoqualifier A qualifier to test
* @return true if the eoqualifier is an EOKeyValueQualifier and its key is a
* key path (with two or more keys) and the last key references a derived
* attribute.
*/
public static boolean isKeyPathWithDerivedAttributeCase(EOEntity entity, EOQualifier eoqualifier) {
// Make sure it's a EOKeyValueQualifier as we need to get a key
if (!(eoqualifier instanceof EOKeyValueQualifier)) {
return false;
}
EOKeyValueQualifier keyValueQualifier = (EOKeyValueQualifier) eoqualifier;
String keyPath = keyValueQualifier.key();
// If it's not a key path with at least two keys then it's not
// the edge case that we are looking for
if (keyPath.contains(".") == false) {
return false;
}
// Traverse the key path to get to last attribute referenced
EOAttribute attr = destinationAttribute(entity, keyPath);
// If the key path lead to an attribute that is derived then it is
// the special case that we checking for.
return attr != null && attr.isDerived();
}
/**
* Returns the last attribute referenced by key path.
*
* @param rootEntity The entity where the key path begins.
* @param keyPath The key path leading to an attribute.
* @return The attribute referenced by the last key in the key path.
* If the last key in the key path is not an attribute then it returns null.
*/
public static EOAttribute destinationAttribute(EOEntity rootEntity, String keyPath) {
// Parse the keys in key path
String[] keys = keyPath.split("\\.");
// Traverse the key path to get to last attribute referenced
EOAttribute attr = null;
EOEntity entity = rootEntity;
for (String key : keys) {
EORelationship relationship = entity.anyRelationshipNamed(key);
if (relationship != null) {
entity = relationship.destinationEntity();
attr = null;
} else {
attr = entity.anyAttributeNamed(key);
}
}
return attr;
}
/**
* Given the definition of a derived attribute belonging to the entity provided
* this method parses the definition looking for key paths that represent properties.
* For example, a customerAge attribute in a hypothetical Order entity could have a
* definition of 'TRUNC(MONTHS_BETWEEN(orderDate,customer.birthDate)/12,0)' and you
* can call this method to get an array containing orderDate and customer.birthDate
* which are the propertyKeys found in the definition.
*
* @param derivedAttribute An EOAttribute with a definition
* @return An array with the key paths referenced by the definition of the derived
* attribute.
*/
public static NSArray<String> parseDefinitionPropertyKeys(EOAttribute derivedAttribute) {
EOEntity entity = derivedAttribute.entity();
String definition = derivedAttribute.definition();
Pattern p = Pattern.compile("('[^']*')|[,]*+\\b([a-z]+[a-zA-Z0-9_\\.]*)");
Matcher m = p.matcher(definition);
NSMutableArray<String> propertyKeys = new NSMutableArray<>();
while (m.find()) {
// Please note that the regular expression has two groups separated
// with an or, i.e. |. The first group in the regular expression
// matches a single-quoted literal which are to be skipped because
// the text within it are not to be considered properties.
if (m.group(1) != null) {
continue;
}
// The second group matches key paths preceded optionally with a comma
// as in the customerAge definition example. So get the key path and if
// consider it a property if it references an attribute when applied to
// the entity.
String keyPath = m.group(2);
if (destinationAttribute(entity, keyPath) != null) {
propertyKeys.add(keyPath);
}
}
return propertyKeys.immutableClone();
}
@Override
public EOQualifier schemaBasedQualifierWithRootEntity(EOQualifier eoqualifier, EOEntity eoentity) {
EOQualifier result = supportForQualifier(eoqualifier).schemaBasedQualifierWithRootEntity(eoqualifier, eoentity);
return result;
}
@Override
public EOQualifier qualifierMigratedFromEntityRelationshipPath(EOQualifier eoqualifier, EOEntity eoentity, String s) {
return supportForQualifier(eoqualifier).qualifierMigratedFromEntityRelationshipPath(eoqualifier, eoentity, s);
}
}
/**
* This method is called every time the configuration file
* is changed. This allows for turning SQL debugging on and
* off at runtime.
* @param n notification posted when the configuration file
* changes.
*/
public void configureAdaptorContext(NSNotification n) {
ERXExtensions.configureAdaptorContext();
}
/**
* This method is called for the following notification
* {@link EOSharedEditingContext#DefaultSharedEditingContextWasInitializedNotification}
*
* @param n the notification.
*/
public void sharedEditingContextWasInitialized(NSNotification n) {
EOSharedEditingContext sec = EOSharedEditingContext.defaultSharedEditingContext();
ERXEC._factory().setDefaultDelegateOnEditingContext(sec, true);
}
/** logging support for the adaptor channel */
public static Logger adaptorLogger;
/** logging support for shared object loading */
public static Logger sharedEOadaptorLogger;
/** flag to indicate if adaptor channel logging is enabled */
private static Boolean adaptorEnabled;
/**
* flag to indicate if rapid turn around is enabled for the
* adaptor channel logging.
*/
private static boolean _isConfigureAdaptorContextRapidTurnAround = false;
/**
* Configures the passed in observer to register a call back
* when the configuration file is changed. This allows one to
* change a logger's setting and have that changed value change
* the NSLog setting to log the generated SQL. This method is
* called as part of the framework initialization process.
* @param anObserver object to register the call back with.
*/
// FIXME: This shouldn't be enabled when the application is in production.
// FIXME: Now that all of the logging has been centralized, we should just be able
// to do something like this, but much more generic, i.e. have a mapping
// between logger names and NSLog groups, for example
// com.webobjects.logging.DebugGroupSQLGeneration we should
// be able to get the last part of the logger name and look up that log group and turn
public static void configureAdaptorContextRapidTurnAround(Object anObserver) {
if (!_isConfigureAdaptorContextRapidTurnAround) {
// This allows enabling from the log4j system.
adaptorLogger = Logger.getLogger("er.transaction.adaptor.EOAdaptorDebugEnabled");
sharedEOadaptorLogger = Logger.getLogger("er.transaction.adaptor.EOSharedEOAdaptorDebugEnabled");
if ((adaptorLogger.isDebugEnabled()
&& !NSLog.debugLoggingAllowedForGroups(NSLog.DebugGroupSQLGeneration|NSLog.DebugGroupDatabaseAccess))
|| ERXProperties.booleanForKey("EOAdaptorDebugEnabled")) {
NSLog.allowDebugLoggingForGroups(NSLog.DebugGroupSQLGeneration|NSLog.DebugGroupDatabaseAccess);
NSLog.setAllowedDebugLevel(NSLog.DebugLevelInformational);
}
adaptorEnabled = NSLog.debugLoggingAllowedForGroups(NSLog.DebugGroupSQLGeneration|NSLog.DebugGroupDatabaseAccess) ? Boolean.TRUE : Boolean.FALSE;
// Allows rapid turn-around of adaptor debugging.
NSNotificationCenter.defaultCenter().addObserver(anObserver,
new NSSelector("configureAdaptorContext", ERXConstant.NotificationClassArray),
ERXConfigurationManager.ConfigurationDidChangeNotification,
null);
_isConfigureAdaptorContextRapidTurnAround = true;
}
}
/**
* This method is called by the delegate when the configuration
* file is changed. It's sole purpose is to map a logging logger
* to a debug group. Hopefully in the future we will have a more
* generic solution.
*/
public static void configureAdaptorContext() {
Boolean targetState = null;
if (adaptorLogger != null) {
if (adaptorLogger.isDebugEnabled() && !adaptorEnabled.booleanValue()) {
targetState = Boolean.TRUE;
} else if (!adaptorLogger.isDebugEnabled() && adaptorEnabled.booleanValue()) {
targetState = Boolean.FALSE;
}
if (targetState != null) {
setAdaptorLogging(targetState.booleanValue());
}
}
}
/**
* Returns the current state of EOAdaptor logging.
*/
public static boolean adaptorLogging() {
return NSLog.debugLoggingAllowedForGroups(NSLog.DebugGroupSQLGeneration|NSLog.DebugGroupDatabaseAccess);
}
/**
* Turn EOAdaptor logging on and off.
* @param onOff
*/
public static void setAdaptorLogging(boolean onOff) {
Boolean targetState = Boolean.valueOf(onOff);
if (NSLog.debugLoggingAllowedForGroups(NSLog.DebugGroupSQLGeneration|NSLog.DebugGroupDatabaseAccess) != targetState.booleanValue()) {
// Post a notification to give us a hook to perform other operations necessary to get logging going, e.g. change Logger settings, etc.
NSNotificationCenter.defaultCenter().postNotification(new NSNotification(eoAdaptorLoggingWillChangeNotification, targetState));
if (targetState.booleanValue()) {
NSLog.allowDebugLoggingForGroups(NSLog.DebugGroupSQLGeneration|NSLog.DebugGroupDatabaseAccess);
} else {
NSLog.refuseDebugLoggingForGroups(NSLog.DebugGroupSQLGeneration|NSLog.DebugGroupDatabaseAccess);
}
}
if (adaptorLogger != null) {
if (targetState.booleanValue()) {
adaptorLogger.info("Adaptor debug on");
} else {
adaptorLogger.info("Adaptor debug off");
}
}
adaptorEnabled = targetState;
}
/**
* Forces the garbage collector to run. The
* max loop parameter determines the maximum
* number of times to run the garbage collector
* if the memory footprint is still going down.
* In normal cases you would just need to call
* this method with the parameter 1. If called
* with the parameter 0 the garbage collector
* will continue to run until no more free memory
* is available to collect.
* <p>
* Note: This can be a very costly operation and
* should only be used in extreme circumstances.
* @param maxLoop maximum times to run the garbage
* collector. Passing in 0 will cause the
* collector to run until all free objects
* have been collected.
*/
public static void forceGC(int maxLoop) {
if (_log.isDebugEnabled()) _log.debug("Forcing full Garbage Collection");
Runtime runtime = Runtime.getRuntime();
long isFree = runtime.freeMemory();
long wasFree;
int i=0;
do {
wasFree = isFree;
runtime.gc();
isFree = runtime.freeMemory();
i++;
} while (isFree > wasFree && (maxLoop<=0 || i<maxLoop) );
runtime.runFinalization(); //TODO: should this be inside the loop?
}
/**
* This method handles 3 different cases
*
* 1. keyPath is a single key and represents a relationship
* --> addObjectToBothSidesOfRelationshipWithKey
* 2. keyPath is a single key and does NOT represents a relationship
* 3. keyPath is a real key path: break it up, navigate to the last atom
* --> back to 1. or 2.
* @param to enterprise object that is having objects added to it
* @param from enterprise object that is providing the objects
* @param keyPath that specifies the relationship on the to object
* to add the objects to.
*/
// MOVEME: ERXEOFUtilities
// FIXME: Should ensure local instances of objects
public static void addObjectToBothSidesOfPotentialRelationshipFromObjectWithKeyPath(EOEnterpriseObject to,
EOEnterpriseObject from,
String keyPath) {
if (from!=null) {
if (keyPath.indexOf('.')!=-1) { // we have a key path
String partialKeyPath=ERXStringUtilities.keyPathWithoutLastProperty(keyPath);
from=(EOEnterpriseObject)from.valueForKeyPath(partialKeyPath);
keyPath=ERXStringUtilities.lastPropertyKeyInKeyPath(keyPath);
}
//if the key is not a keyPath we can check if the key is actually a relationship
EOEntity e=ERXEOAccessUtilities.entityNamed(from.editingContext(), from.entityName());
EORelationship r=e.relationshipNamed(keyPath);
if (r!=null) //if the key correspond to a relationship
from.addObjectToBothSidesOfRelationshipWithKey(to, keyPath);
else
from.takeValueForKeyPath(to,keyPath);
}
}
/**
* For a given enterprise object and key path, will return what
* the key 'unit' returns from the userInfo dictionary of the
* last property of the key path's EOAttribute or EORelationship.
* The userInfo dictionary can be edited via EOModeler, it is that
* open book looking icon when looking at either an attribute or
* relationship.
* <p>
* For example if the userInfo dictionary or the attribute 'speed' on the
* entity Car contained the key-value pair unit=mph, then this method
* would be able to resolve that unit given either of these keypaths:
* <pre><code>userInfoUnit(aCar, "speed");
*userInfoUnit(aDrive, "toCar.speed");</code></pre>
* Units can be very useful for adding meta information to particular
* attributes and relationships in models. The ERDirectToWeb framework
* adds support for displaying units.
* @param object to resolve the key-path from
* @param key path off of the object
* @return unit information stored in the userInfo dictionary
*/
// ENHANCEME: Should be more generic for resolving any key off of the userInfo
// dictionary.
// ENHANCEME: Should also support defaulting to the same attribute in the parent entity
// if it isn't found or possibly defaulting to the entity's userInfo itself
// MOVEME: ERXEOFUtilities
public static String userInfoUnit(EOEnterpriseObject object, String key) {
// return the unit stored in the userInfo dictionary of the appropriate EOAttribute
EOEntity entity=null;
String lastKey=null;
String result=null;
if (key.indexOf(".")==-1) {
String entityName=object.entityName();
entity=ERXEOAccessUtilities.entityNamed(object.editingContext(), entityName);
lastKey=key;
} else {
String partialKeyPath=ERXStringUtilities.keyPathWithoutLastProperty(key);
EOEnterpriseObject objectForPropertyDisplayed=(EOEnterpriseObject)object.valueForKeyPath(partialKeyPath);
if (objectForPropertyDisplayed!=null) {
entity=ERXEOAccessUtilities.entityNamed(object.editingContext(), objectForPropertyDisplayed.entityName());
lastKey=ERXStringUtilities.lastPropertyKeyInKeyPath(key);
}
}
if (entity!=null && lastKey!=null) {
EOAttribute a=entity.attributeNamed(lastKey);
NSDictionary userInfo=null;
if (a!=null) userInfo=a.userInfo();
else {
EORelationship r=entity.relationshipNamed(lastKey);
if (r!=null) userInfo=r.userInfo();
}
result= (String)(userInfo!=null ? userInfo.valueForKey("unit") : null);
}
return result;
}
/**
* Resolves a given user info unit string for a given object.
* User info values are stored in an EOAttibute or EORelationship's
* userInfo dictionary.
* See the method {@link #userInfoUnit(EOEnterpriseObject, String) userInfoUnit} for
* a better description of getting values out of the userInfo
* dictionary. This method deals with resolving dynamic userInfo
* keys. These keys need to start with the '@@' symbol. For instance
* if you have the user info value '@unit' off of an attribute for the
* entity Movie, then you can either pass in a Movie object or a
* different object with a prefix key path to a movie object.
*
* @param userInfoUnitString string to be resolved, needs to start with
* '@@'. This keypath will be evaluated against either the object
* if no prefixKeyPath is specified or the object returned by
* the prefixKeyPath being evaluated against the object passed in.
* @param object to resolve either the user info unit or the prefixKeyPath.
* @param prefixKeyPath used as a prefix for the unit resolution.
* @return the resolved unit from the object.
*/
public static String resolveUnit(String userInfoUnitString,
EOEnterpriseObject object,
String prefixKeyPath) {
// some of our units (stored in the user info) take the form of @project.unit
// this method resolves the @keyPath..
if(userInfoUnitString!=null && userInfoUnitString.indexOf("@")>-1){
String keyPath = userInfoUnitString.substring(1);
String PropertyKeyWithoutLastProperty = ERXStringUtilities.keyPathWithoutLastProperty(prefixKeyPath);
EOEnterpriseObject objectForPropertyDisplayed = null;
if(PropertyKeyWithoutLastProperty!=null){
objectForPropertyDisplayed = object!=null ? (EOEnterpriseObject)object.valueForKeyPath(PropertyKeyWithoutLastProperty) : null;
}else{
objectForPropertyDisplayed = object;
}
userInfoUnitString = objectForPropertyDisplayed!=null ? (String)objectForPropertyDisplayed.valueForKeyPath(keyPath) : null;
}
return userInfoUnitString;
}
/**
* Refreshes all of the objects for an array of entity names.
* @param names array of shared entity names
*/
// FIXME: Uses default model group.
// MOVEME: ERXEOFUtilities
public static void refreshSharedObjectsWithNames(NSArray names) {
for (Enumeration e = names.objectEnumerator(); e.hasMoreElements();)
refreshSharedObjectsWithName((String)e.nextElement());
}
private static NSSelector _sharedEntityDataWasRefreshedSelector = new NSSelector("sharedEntityDataWasRefreshed");
/**
* Refreshes all of the shared enterprise objects for a given shared entity,
* then notifies the entity's class by calling the static method
* sharedEntityDataWasRefreshed() if the shared entity implements it.
*
* @param entityName name of the shared entity
*/
// FIXME: Uses default model group, and default shared editing context.
// MOVEME: ERXEOFUtilities
public static void refreshSharedObjectsWithName(String entityName) {
if (entityName == null) {
throw new IllegalArgumentException("Entity name argument is null for method: refreshSharedObjectsWithName");
}
EOSharedEditingContext sharedEC = EOSharedEditingContext.defaultSharedEditingContext();
sharedEC.lock();
try {
EOEntity entity = ERXEOAccessUtilities.entityNamed(sharedEC, entityName);
if (entity == null) {
_log.warn("Attempting to refresh a non-existent (or not accessible) EO: " + entityName);
return;
}
//if entity caches objects, clear out the cache
if( entity.cachesObjects() ) {
EODatabaseContext databaseContext = EOUtilities.databaseContextForModelNamed(sharedEC, entity.model().name());
EODatabase database = databaseContext.database();
database.invalidateResultCacheForEntityNamed(entityName);
}
NSArray fetchSpecNames = entity.sharedObjectFetchSpecificationNames();
int count = (fetchSpecNames != null) ? fetchSpecNames.count() : 0;
if ( count > 0 ) { //same check as ERXEOAccessUtilities.entityWithNamedIsShared(), but avoids duplicate work
for (int index = 0 ; index < count ; ++index) {
String oneFetchSpecName = (String)fetchSpecNames.objectAtIndex(index);
EOFetchSpecification fs = entity.fetchSpecificationNamed(oneFetchSpecName);
if (fs != null) {
fs.setRefreshesRefetchedObjects(true);
sharedEC.bindObjectsWithFetchSpecification(fs, oneFetchSpecName);
}
}
//notify the entity class, if it wants to know
String className = entity.className();
Class entityClass = Class.forName(className);
if (_sharedEntityDataWasRefreshedSelector.implementedByClass(entityClass)) {
_sharedEntityDataWasRefreshedSelector.invoke(entityClass);
}
} else {
_log.warn("Attempting to refresh a non-shared EO: " + entityName);
}
} catch (Exception e) {
throw new NSForwardException(e, "Exception while refreshing shared objects for entity named " + entityName);
} finally {
sharedEC.unlock();
}
}
/**
* This method can be used with Direct Action URLs to make sure
* that the browser will reload the page. This is done by
* adding the parameter [? | &]r=random_number to the end of the
* url.
* @param daURL a url to add the randomization to.
* @return url with the addition of the randomization key
*/
// FIXME: Should check to make sure that the key 'r' isn't already present in the url.
public static String randomizeDirectActionURL(String daURL) {
int r=ThreadLocalRandom.current().nextInt();
char c=daURL.indexOf('?')==-1 ? '?' : '&';
return daURL+c+"r="+r;
}
/**
* This method can be used with Direct Action URLs to make sure
* that the browser will reload the page. This is done by
* adding the parameter [? | &]r=random_number to the end of the
* url.
* @param daURL a url to add the randomization to.
*/
// FIXME: Should check to make sure that the key 'r' isn't already present in the url.
public static void addRandomizeDirectActionURL(StringBuffer daURL) {
int r=ThreadLocalRandom.current().nextInt();
char c='?';
for (int i=0; i<daURL.length(); i++) {
if (daURL.charAt(i)=='?') {
c='&'; break;
}
}
daURL.append(c);
daURL.append("r=");
daURL.append(r);
}
/**
* Adds the session ID for a given session to a given URL.
*
* @param urlString
* URL string to add session ID form value to
* @param session
* session object
* @return URL with the addition of session ID form value
*/
public static String addSessionIdFormValue(String urlString, WOSession session) {
if (urlString == null || session == null) {
_log.warn("not adding session ID: url=" + (urlString != null ? urlString : "<null>") + " session=" + (session != null ? session : "<null>"));
return urlString;
}
String sessionIdKey = WOApplication.application().sessionIdKey();
try {
ERXMutableURL url = new ERXMutableURL(urlString);
if (!url.containsQueryParameter(sessionIdKey)) {
url.setQueryParameter(sessionIdKey, session.sessionID());
}
return url.toExternalForm();
}
catch (MalformedURLException e) {
_log.error("invalid URL string: " + urlString, e);
}
return urlString;
}
/**
* Uses the <code>setObjectForKey</code> method of the {@link WOSession}
* class to push a Boolean object onto the session for a given key.
* Note this is not using key value coding, meaning you don't need
* to have a boolean instance variable corresponding to the given
* key on your session object. This flag can be retrieved using
* the method <code>booleanFlagOnSessionForKeyWithDefault</code>.
* @param s session object on which to set the boolean flag
* @param key to be used in the session's dictionary
* @param newValue boolean value to be set on the session
*/
public static void setBooleanFlagOnSessionForKey(WOSession s,
String key,
boolean newValue) {
s.setObjectForKey(newValue ? Boolean.TRUE : Boolean.FALSE, key);
}
/**
* Retrieves a value from the session's dictionary and evaluates
* that object using the <code>booleanValue</code> method of
* {@link ERXValueUtilities}. If there is no object corresponding
* to the key passed in, then the default value is returned. The
* usual way in which boolean values are set on the session object
* is by using the method <code>setBooleanFlagOnSessionForKey</code>
* in this class.
* @param s session object to retrieve the boolean flag from
* @param key that the boolean is stored under
* @param defaultValue value to be returned if the object in the
* dictionary is null
* @return boolean value of the object stored in the session's dictionary
* for the given key.
*/
public static boolean booleanFlagOnSessionForKeyWithDefault(WOSession s,
String key,
boolean defaultValue) {
return s.objectForKey(key) != null ? ERXValueUtilities.booleanValue(s.objectForKey(key)) : defaultValue;
}
/**
* Constructs a unique key based on a context.
* A method used by the preferences mechanism from ERDirectToWeb which
* needs to be here because it is shared by ERDirectToWeb and ERCoreBusinessLogic.
*
* @param key preference key
* @param context most likely a d2wContext object
* @return a unique preference key for storing and retrieving preferences
*/
// FIXME: Needs to find a better home.
public static String userPreferencesKeyFromContext(String key, NSKeyValueCoding context) {
StringBuilder result = new StringBuilder(key);
result.append('.');
String pc=(String)context.valueForKey("pageConfiguration");
if (pc==null || pc.length()==0) {
String en="_All_";
EOEntity e=(EOEntity)context.valueForKey("entity");
if (e!=null) en=e.name();
result.append("__");
result.append(context.valueForKey("task"));
result.append('_');
result.append(en);
} else {
result.append(pc);
}
return result.toString();
}
/**
* Frees all of the resources associated with a given
* process and then destroys the process.
* @param p process to destroy
* @deprecated use {@link ERXRuntimeUtilities#freeProcessResources(Process)} instead
*/
@Deprecated
public static void freeProcessResources(Process p) {
if (p!=null) {
try {
if (p.getInputStream()!=null) p.getInputStream().close();
if (p.getOutputStream()!=null) p.getOutputStream().close();
if (p.getErrorStream()!=null) p.getErrorStream().close();
p.destroy();
} catch (IOException e) {}
}
}
/**
* Determines if a given object implements a method given
* the name and the array of input parameters.
* Note that this doesn't quite check the method signature
* since the method return type is not checked.
*
* @param object to determine if it implements a method
* @param methodName name of the method
* @param parameters array of parameters
* @return if the object implements a method with the given name
* and class parameters
*/
public static boolean objectImplementsMethod(Object object, String methodName, Class[] parameters) {
boolean implementsMethod = false;
for (Enumeration e = (new NSArray(object.getClass().getMethods())).objectEnumerator(); e.hasMoreElements();) {
Method m = (Method)e.nextElement();
if (m.getName().equals(methodName) && Arrays.equals(m.getParameterTypes(), parameters)) {
implementsMethod = true; break;
}
}
return implementsMethod;
}
/**
* Initializes your WOApplication programmatically (for use in test cases and main methods) with
* the assumption that the current directory is your main bundle URL.
*
* @param applicationSubclass your Application subclass
* @param args the commandline arguments for your application
*/
public static void initApp(Class applicationSubclass, String[] args) {
try {
File woaFolder = new File(".").getCanonicalFile();
if (!woaFolder.getName().endsWith(".woa")) {
if (new File(woaFolder, ".project").exists()) {
File buildFolder = new File(new File(woaFolder, "build"), woaFolder.getName() + ".woa");
if (buildFolder.exists()) {
woaFolder = buildFolder;
}
else {
File distFolder = new File(new File(woaFolder, "dist"), woaFolder.getName() + ".woa");
if (distFolder.exists()) {
woaFolder = distFolder;
}
else {
//Bundle-less builds. Yay!
//throw new IllegalArgumentException("You must run your application from a .woa folder to call this method.");
}
}
}
}
ERXExtensions.initApp(null, woaFolder.toURI().toURL(), applicationSubclass, args);
}
catch (IOException e) {
throw new NSForwardException(e);
}
}
/**
* Initializes your WOApplication programmatically (for use in test cases and main methods).
*
* @param mainBundleName the name of your main bundle
* @param applicationSubclass your Application subclass
* @param args the commandline arguments for your application
*/
public static void initApp(String mainBundleName, Class applicationSubclass, String[] args) {
ERXExtensions.initApp(mainBundleName, null, applicationSubclass, args);
}
private static boolean _appInitialized = false;
/**
* Initializes your WOApplication programmatically (for use in test cases and main methods).
*
* @param mainBundleName the name of your main bundle (or null to use mainBundleURL)
* @param mainBundleURL the URL to your main bundle (ignored if mainBundleName is set)
* @param applicationSubclass your Application subclass
* @param args the commandline arguments for your application
*/
public static void initApp(String mainBundleName, URL mainBundleURL, Class applicationSubclass, String[] args) {
if (_appInitialized) {
return;
}
try {
ERXApplication.setup(args);
if (mainBundleURL != null) {
System.setProperty("webobjects.user.dir", new File(mainBundleURL.getFile()).getCanonicalPath());
}
// Odds are you are only using this method for test cases and development mode
System.setProperty("er.extensions.ERXApplication.developmentMode", "true");
ERXApplication.primeApplication(mainBundleName, mainBundleURL, applicationSubclass.getName());
//NSNotificationCenter.defaultCenter().postNotification(new NSNotification(ERXApplication.ApplicationDidCreateNotification, WOApplication.application()));
}
catch (IOException e) {
throw new NSForwardException(e);
}
_appInitialized = true;
}
/**
* Initializes Wonder EOF programmatically (for use in test cases and main methods). You do
* not need to call this method if you already called initApp. This is lighter-weight than
* initApp, and tries to just get enough configured to make EOF work properly. This method
* assumes you are running your app from a .woa folder.
*
* <p>This is equivalent to calling <code>initEOF(new File("."), args)</code>.</p>
*
* @param args the commandline arguments for your application
* @throws IllegalArgumentException if the current dir or mainBundleFolder is not a *.woa bundle.
*/
public static void initEOF(final String[] args) {
ERXExtensions.initEOF(new File("."), args);
}
/**
* Initializes Wonder EOF programmatically (for use in test cases and main methods). You do
* not need to call this method if you already called initApp. This is lighter-weight than
* initApp, and tries to just get enough configured to make EOF work properly.
*
* <p>This is equivalent to calling <code>initEOF(mainBundleFolder, args, true, true, true)</code>.</p>
*
* @param mainBundleFolder the folder of your main bundle
* @param args the commandline arguments for your application
* @throws IllegalArgumentException if the current dir or mainBundleFolder is not a *.woa bundle.
*/
public static void initEOF(final File mainBundleFolder, final String[] args) {
initEOF(mainBundleFolder, args, true, true, true);
}
/**
* <p>
* Initializes Wonder EOF programmatically (for use in test cases and main methods). You do
* not need to call this method if you already called initApp. This is lighter-weight than
* initApp, and tries to just get enough configured to make EOF work properly. This method is also,
* unlike {@link #initEOF(String[])} or {@link #initEOF(File, String[])}, not so restrictive as to
* require the name of the bundle be a <code>*.woa</code>, and nor does it require <code>*.framework</code>
* as the name of the bundle.
* </p>
* <p>
* It can therefore be useful for, and used with, folder, jar or war
* bundles -- whichever bundle is referenced by <code>mainBundleURI</code> -- so long as it is
* bundle-like in content rather than by name. For NSBundle, this usually just requires a Resources folder
* within the bundle.
* </p>
* <p>
* For example, if you're build tool compiles sources and puts Resources under <code>target/classes</code>
* you can call this method via <code>ERXExtensions.initEOF(new File(projectDir, "target/classes"), args, true)</code>.
* </p>
* <p><b>Note 1:</b>
* this will set the system property <code>webobjects.user.dir</code> to the canonical path of the
* given bundle uri if, and only if, the bundle uri points to a directory and is schema is <code>file</code>.
* </p>
* <p><b>Note 2:</b>
* this will set NSBundle's mainBundle to the referenced bundle loaded via
* {@link er.extensions.foundation.ERXRuntimeUtilities#loadBundleIfNeeded(File)} if found.
* </p>
*
* <p>This is equivalent to calling <code>initEOF(mainBundleFolder, args, assertsBundleExists, false, true)</code>.</p>
*
* @param mainBundleFile the archive file or directory of your main bundle
* @param args the commandline arguments for your application
* @param assertsBundleExists ensures that the bundle exists and is loaded
* @throws NSForwardException if the given bundle doesn't satisfy the given assertions or
* ERXRuntimeUtilities.loadBundleIfNeeded or ERXApplication.setup fails.
* @see #initEOF(File, String[], boolean, boolean, boolean)
*/
public static void initEOF(final File mainBundleFile, final String[] args, boolean assertsBundleExists) {
initEOF(mainBundleFile, args, assertsBundleExists, false, true);
}
private static boolean _eofInitialized = false;
private static final Lock _eofInitializeLock = new ReentrantLock();
/**
* <p>
* Initializes Wonder EOF programmatically (for use in test cases and main methods). You do
* not need to call this method if you already called initApp. This is lighter-weight than
* initApp, and tries to just get enough configured to make EOF work properly. This method is also,
* unlike {@link #initEOF(String[])} or {@link #initEOF(File, String[])}, not so restrictive as to
* require the name of the bundle be a <code>*.woa</code>, and nor does it require <code>*.framework</code>
* as the name of the bundle.
* </p>
* <p>
* It can therefore be useful for, and used with, folder, jar or war
* bundles -- whichever bundle is referenced by <code>mainBundleURI</code> -- so long as it is
* bundle-like in content rather than by name. For NSBundle, this usually just requires a Resources folder
* within the bundle.
* </p>
* <p>
* For example, if you're build tool compiles sources and puts Resources under <code>target/classes</code>
* you can call this method via <code>ERXExtensions.initEOF(new File(projectDir, "target/classes").toURI(), args)</code>.
* </p>
* <p><b>Note 1:</b>
* this will set the system property <code>webobjects.user.dir</code> to the canonical path of the
* given bundle uri if, and only if, the bundle uri points to a directory and is schema is <code>file</code>.
* </p>
* <p><b>Note 2:</b>
* this will set NSBundle's mainBundle to the referenced bundle loaded via
* {@link er.extensions.foundation.ERXRuntimeUtilities#loadBundleIfNeeded(File)} if found.
* </p>
*
* @param mainBundleFile the archive file or directory of your main bundle
* @param args the commandline arguments for your application
* @param assertsBundleExists ensures that the bundle exists and is loaded
* @param assertsBundleIsWOApplicationFolder ensures that the bundle referenced by mainBundleFile, or the current dir if fallbackToUserDirAsBundle is true, is a <code>*.woa</code> bundle folder.
* @param fallbackToUserDirAsBundle falls back to current dir if the mainBundleFile does not exist
* @throws NSForwardException if the given bundle doesn't satisfy the given assertions or
* ERXRuntimeUtilities.loadBundleIfNeeded or ERXApplication.setup fails.
* @see er.extensions.foundation.ERXRuntimeUtilities#loadBundleIfNeeded(File)
* @see NSBundle#_setMainBundle(NSBundle)
* @see er.extensions.appserver.ERXApplication#setup(String[])
* @see #bundleDidLoad(NSNotification)
*/
public static void initEOF(final File mainBundleFile, String[] args, boolean assertsBundleExists, boolean assertsBundleIsWOApplicationFolder, boolean fallbackToUserDirAsBundle) {
_eofInitializeLock.lock();
try {
if (!_eofInitialized) {
try {
File bundleFile = mainBundleFile;
if (assertsBundleIsWOApplicationFolder) {
if (!bundleFile.exists() || !bundleFile.getName().endsWith(".woa")) {
bundleFile = new File(".").getCanonicalFile();
if (!bundleFile.exists() || !bundleFile.getName().endsWith(".woa")) {
throw new IllegalArgumentException("Assertion failure. You must run your application from the .woa folder to call this method.");
}
}
}
if (assertsBundleExists) {
if (bundleFile == null || !bundleFile.exists()) {
if (fallbackToUserDirAsBundle) {
bundleFile = new File(".").getCanonicalFile();
} else {
throw new IllegalArgumentException("Assertion failure. The main bundle is required to exist to call this method.");
}
}
}
if (bundleFile != null && bundleFile.isDirectory()) {
System.setProperty("webobjects.user.dir", bundleFile.getCanonicalPath());
}
NSBundle mainBundle = null;
try {
mainBundle = ERXRuntimeUtilities.loadBundleIfNeeded(bundleFile);
if (mainBundle == null) {
throw new IllegalArgumentException("The main bundle failed to load.");
}
NSBundle._setMainBundle(mainBundle);
NSLog.debug.appendln("initEOF setting main bundle to " + mainBundle);
}
catch (Exception e) {
if (assertsBundleExists) {
throw e;
}
}
ERXApplication.setup(args);
ERXFrameworkPrincipal.sharedInstance(ERXExtensions.class).bundleDidLoad(null);
}
catch (Exception e) {
throw new NSForwardException(e);
}
_eofInitialized = true;
}
} finally {
_eofInitializeLock.unlock();
}
}
}