/**
*
*/
package com.webobjects.eoaccess;
import java.net.URL;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSDictionary;
import com.webobjects.foundation.NSMutableDictionary;
import com.webobjects.jdbcadaptor.JDBCAdaptor;
import er.extensions.eof.ERXEOAccessUtilities;
import er.extensions.eof.ERXKey;
import er.extensions.eof.ERXModelGroup;
import er.extensions.foundation.ERXProperties;
/**
* This EOModel subclass primarily provides the opportunity to subclass EOEntity.
*
* <p><b>Note</b> the package <code>com.webobjects.eoaccess</code> is used to
* allow any protected or default access superclass instance methods to resolve
* at runtime.
*
* <p>To allow for extended prototypes set
* <code>er.extensions.ERXModel.useExtendedPrototypes=true</code>.
* Note: this may be incompatible with {@link er.extensions.eof.ERXModelGroup#flattenPrototypes}.</p>
*
* <p>The existence of prototype entities based on specific conventions
* is checked and the attributes of those prototype entities are added to the model's
* prototype attributes cache in a specific order. The search order ensures that
* the same prototype attribute names in different prototype entities get chosen
* in a predictable way.</p>
*
* <p>Consequently, you can use this search order knowledge to over-ride Wonder's
* ERPrototypes for your entire set of application eomodels or just for specific
* named eomodels.</p>
*
* To understand the variables used in deriving the prototype entity names that are searched
* a few definitions are appropriate
* <dl>
* <dt><pluginName></dt>
* <dd>Relates to the database type. Examples of pluginName are MySQL, Derby, FrontBase, OpenBase, Oracle, Postgresql</dd>
* <dt><adaptorName></dt>
* <dd>Relates to the general persistence mechanism. Examples of adaptorName are JDBC, Memory, REST</dd>
* <dt><modelName></dt>
* <dd>The name of an eomodel in your app or frameworks</dd>
*
* </dl>
*
* The priority order (which is basically the reverse of the search order)
* for prototype entities is as follows:
* <ul>
* <li>EOJDBC<pluginName><modelname>Prototypes</li>
* <li>EO<adaptorName><modelname>Prototypes</li>
* <li>EO<modelname>Prototypes</li>
* <li>EOJDBC<pluginName>CustomPrototypes</li>
* <li>EO<adaptorName>CustomPrototypes</li>
* <li>EOCustomPrototypes</li>
* <li>EOJDBC<pluginName>Prototypes <em>(Available for popular databases in ERPrototypes framework)</em></li>
* <li>EO<adaptorName>Prototypes <em>(ERPrototypes has some of these too for generic-JDBC, Memory, etc.)</em></li>
* <li>EOPrototypes <em>(Without ERXModel and the extendedPrototypes, this was pretty much your only way to add your own prototypes alongside ERPrototypes)</em></li>
* </ul>
*
* @author ldeck
*/
public class ERXModel extends EOModel {
// Expose EOModel._EOGlobalModelLock so that ERXModelGroup can lock on it
public static Object _ERXGlobalModelLock = EOModel._EOGlobalModelLock;
private static final Logger log = LoggerFactory.getLogger(ERXModel.class);
/**
* Utility to add attributes to the prototype cache. As the attributes are chosen by name, replace already
* existing ones.
*
* @param model - the model to which the prototype attributes will be cached
* @param prototypesEntity - the entity from which to copy the prototype attributes
*/
private static void addAttributesToPrototypesCache(EOModel model, EOEntity prototypesEntity) {
if (model != null && prototypesEntity != null) {
addAttributesToPrototypesCache(model, attributesFromEntity(prototypesEntity));
}
}
/**
* Utility to add attributes to the prototype cache for a given model. As the attributes are chosen by name, replace already
* existing ones.
*
* @param model - the model to which the prototype attributes will be cached
* @param prototypeAttributes - the prototype attributes to add to the model
*/
private static void addAttributesToPrototypesCache(EOModel model, NSArray<? extends EOAttribute> prototypeAttributes) {
if (model != null && prototypeAttributes.count() != 0) {
NSArray keys = namesForAttributes(prototypeAttributes);
NSDictionary temp = new NSDictionary(prototypeAttributes, keys);
model._prototypesByName.addEntriesFromDictionary(temp);
}
}
/**
* Utility for getting all the attributes off an entity. If the entity is null, an empty array is returned.
*
* @param entity an entity
* @return array of attributes from the given entity
*/
private static NSArray<EOAttribute> attributesFromEntity(EOEntity entity) {
NSArray<EOAttribute> result = NSArray.emptyArray();
if (entity != null) {
result = entity.attributes();
log.debug("Attributes from {}: {}", entity.name(), result);
}
return result;
}
/**
* Create the prototype cache for the given model by walking a search order.
* @param model
*/
public static void createPrototypes(EOModel model) {
// Remove password for logging
NSMutableDictionary dict = model.connectionDictionary().mutableClone();
if (dict.objectForKey("password") != null) {
dict.setObjectForKey("<deleted for log>", "password");
}
log.info("Creating prototypes for model: {}->{}", model.name(), dict);
synchronized (_EOGlobalModelLock) {
StringBuilder debugInfo = null;
if (log.isDebugEnabled()) {
debugInfo = new StringBuilder();
debugInfo.append("Model = " + model.name());
}
model._prototypesByName = new NSMutableDictionary();
String name = model.name();
NSArray adaptorPrototypes = NSArray.EmptyArray;
EOAdaptor adaptor = EOAdaptor.adaptorWithModel(model);
try {
adaptorPrototypes = adaptor.prototypeAttributes();
}
catch (Exception e) {
log.error("Could not get prototype attributes from adaptor.", e);
}
addAttributesToPrototypesCache(model, adaptorPrototypes);
NSArray prototypesToHide = attributesFromEntity(model._group.entityNamed("EOPrototypesToHide"));
model._prototypesByName.removeObjectsForKeys(namesForAttributes(prototypesToHide));
String plugin = null;
if (adaptor instanceof JDBCAdaptor && !model.name().equalsIgnoreCase("erprototypes")) {
plugin = (String) model.connectionDictionary().objectForKey("plugin");
if (plugin == null) {
plugin = ERXEOAccessUtilities.guessPluginName(model);
} //~ if (plugin == null)
if (plugin != null && plugin.toLowerCase().endsWith("plugin")) {
plugin = plugin.substring(0, plugin.length() - "plugin".length());
}
if (log.isDebugEnabled()) debugInfo.append("; plugin = " + plugin);
}
addAttributesToPrototypesCache(model, model._group.entityNamed("EOPrototypes"));
addAttributesToPrototypesCache(model, model._group.entityNamed("EO" + model.adaptorName() + "Prototypes"));
if (log.isDebugEnabled()) debugInfo.append("; Prototype Entities Searched = EOPrototypes, " + "EO" + model.adaptorName() + "Prototypes");
if (plugin != null) {
addAttributesToPrototypesCache(model, model._group.entityNamed("EOJDBC" + plugin + "Prototypes"));
if (log.isDebugEnabled()) debugInfo.append(", " + "EOJDBC" + plugin + "Prototypes");
}
addAttributesToPrototypesCache(model, model._group.entityNamed("EOCustomPrototypes"));
addAttributesToPrototypesCache(model, model._group.entityNamed("EO" + model.adaptorName() + "CustomPrototypes"));
if (log.isDebugEnabled()) debugInfo.append(", EOCustomPrototypes, " + "EO" + model.adaptorName() + "CustomPrototypes");
if (plugin != null) {
addAttributesToPrototypesCache(model, model._group.entityNamed("EOJDBC" + plugin + "CustomPrototypes"));
if (log.isDebugEnabled()) debugInfo.append(", " + "EOJDBC" + plugin + "CustomPrototypes");
}
addAttributesToPrototypesCache(model, model._group.entityNamed("EO" + name + "Prototypes"));
addAttributesToPrototypesCache(model, model._group.entityNamed("EO" + model.adaptorName() + name + "Prototypes"));
if (log.isDebugEnabled()) debugInfo.append(", " + "EO" + name + "Prototypes" + ", " + "EO" + model.adaptorName() + name + "Prototypes");
if (plugin != null) {
addAttributesToPrototypesCache(model, model._group.entityNamed("EOJDBC" + plugin + name + "Prototypes"));
if (log.isDebugEnabled()) debugInfo.append(", " + "EOJDBC" + plugin + name + "Prototypes");
}
if (log.isDebugEnabled()) log.debug(debugInfo.toString());
}
}
/**
* Utility for getting all names from an array of attributes.
*
* @param attributes array of attributes
* @return array of attribute names
*/
private static NSArray<String> namesForAttributes(NSArray<? extends EOAttribute> attributes) {
return new ERXKey<String>("name").arrayValueInObject(attributes);
}
/**
* Defaults to false.
* Note: when enabled, this may be incompatible with {@link er.extensions.eof.ERXModelGroup#flattenPrototypes}.
* @return the boolean property value for <code>er.extensions.ERXModel.useExtendedPrototypes</code>.
*/
public static boolean isUseExtendedPrototypesEnabled() {
return ERXProperties.booleanForKeyWithDefault("er.extensions.ERXModel.useExtendedPrototypes", false);
}
/**
* Creates and returns a new ERXModel.
*/
public ERXModel() {
super();
}
/**
* Creates a new EOModel object by reading the contents of the model archive
* at url. Sets the EOModel's name and path from the context of the model
* archive. Throws an IllegalArgumentException if url is null or if unable
* to read content from url. Throws a runtime exception if unable for any
* other reason to initialize the model from the specified java.net.URL;
* the error text indicates the nature of the exception.
*
* @param url - The java.net.URL to a model archive.
*/
public ERXModel(URL url) {
super(url);
}
/**
* @param propertyList
* @param path
*/
public ERXModel(NSDictionary propertyList, String path) {
super(propertyList, path);
}
/**
* @param propertyList
* @param url
*/
public ERXModel(NSDictionary propertyList, URL url) {
super(propertyList, url);
}
/**
* Sets the default EOEntity class to com.webobjects.eoaccess.ERXEntity. You can provide your
* own via the property <code>er.extensions.ERXModel.defaultEOEntityClassName</code> however your class
* must be in the same package unless you plan on re-implementing eof itself.
*
* @see com.webobjects.eoaccess.EOModel#_addEntityWithPropertyList(java.lang.Object)
*/
@Override
public Object _addEntityWithPropertyList(Object propertyList) throws InstantiationException, IllegalAccessException {
NSMutableDictionary<String, Object> list = ((NSDictionary<String, Object> )propertyList).mutableClone();
if (list.objectForKey("entityClass") == null) {
String eoEntityClassName = ERXProperties.stringForKey("er.extensions.ERXModel.defaultEOEntityClassName");
if (eoEntityClassName == null) {
eoEntityClassName = ERXEntity.class.getName();
}
list.setObjectForKey(eoEntityClassName, "entityClass" );
}
return super._addEntityWithPropertyList(list);
}
/**
* Overridden to use our prototype creation method if
* <code>er.extensions.ERXModel.useExtendedPrototypes=true</code>.
*/
@Override
public NSArray availablePrototypeAttributeNames() {
synchronized (_EOGlobalModelLock) {
if (_prototypesByName == null && useExtendedPrototypes()) {
createPrototypes(this);
}
}
return super.availablePrototypeAttributeNames();
}
/**
* Overridden to use our prototype creation method if
* <code>er.extensions.ERXModel.useExtendedPrototypes=true</code>.
*/
@Override
public EOAttribute prototypeAttributeNamed(String name) {
synchronized (_EOGlobalModelLock) {
if (_prototypesByName == null && useExtendedPrototypes()) {
createPrototypes(this);
}
}
return super.prototypeAttributeNamed(name);
}
@Override
public void setModelGroup(EOModelGroup modelGroup) {
super.setModelGroup(modelGroup);
if (modelGroup instanceof ERXModelGroup) {
((ERXModelGroup) modelGroup).resetConnectionDictionaryInModel(this);
}
}
/**
* Defaults to false as returned by {@link #isUseExtendedPrototypesEnabled()}.
* @return <code>true</code> if extended prototypes are used
* @see #isUseExtendedPrototypesEnabled()
*/
protected boolean useExtendedPrototypes() {
return isUseExtendedPrototypesEnabled();
}
}