/* * 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; import java.net.URL; import java.util.Enumeration; import org.apache.log4j.Logger; import com.webobjects.appserver.WOComponent; import com.webobjects.appserver.WOContext; import com.webobjects.appserver.WOSession; import com.webobjects.directtoweb.D2W; import com.webobjects.directtoweb.D2WContext; import com.webobjects.directtoweb.D2WPage; import com.webobjects.directtoweb.ERD2WContext; import com.webobjects.directtoweb.KeyValuePath; import com.webobjects.eoaccess.EOEntity; import com.webobjects.eoaccess.EOModelGroup; import com.webobjects.eocontrol.EOEnterpriseObject; import com.webobjects.eocontrol.EOSortOrdering; 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 er.directtoweb.pages.ERD2WPage; import er.extensions.ERXExtensions; import er.extensions.ERXFrameworkPrincipal; import er.extensions.appserver.ERXSession; import er.extensions.appserver.ERXWOContext; import er.extensions.eof.ERXConstant; import er.extensions.foundation.ERXArrayUtilities; import er.extensions.foundation.ERXConfigurationManager; import er.extensions.foundation.ERXFileUtilities; import er.extensions.foundation.ERXKeyValuePair; import er.extensions.foundation.ERXPatcher; import er.extensions.foundation.ERXProperties; import er.extensions.foundation.ERXValueUtilities; import er.extensions.localization.ERXLocalizer; /** * Principle class of the ERDirectToWeb framework. * This class is loaded when the NSBundle of this * framework is loaded. When loaded this class configures * the directtoweb runtime to use the {@link er.directtoweb.ERD2WModel} and * {@link er.directtoweb.ERD2WFactory} subclasses instead of the default * implementations. See each class for a description of the * additions/improvements made to the base implementation. * This class also has a bunch of utility methods that are * used throughout this framework. */ public class ERDirectToWeb extends ERXFrameworkPrincipal { public final static Class REQUIRES[] = new Class[] {ERXExtensions.class}; /** logging support */ public final static Logger log = Logger.getLogger("er.directtoweb.ERDirectToWeb"); public final static String D2WDEBUGGING_ENABLED_KEY = "ERDirectToWeb_d2wDebuggingEnabled"; public final static String D2WDISPLAY_COMPONENTNAMES_KEY = "ERDirectToWeb_displayComponentNames"; public final static String D2WDISPLAY_PROPERTYKEYS_KEY = "ERDirectToWeb_displayPropertyKeys"; public final static String D2WDISPLAY_PAGE_METRICS_KEY = "ERDirectToWeb_displayPageMetrics"; public final static String D2WDISPLAY_DETAILED_PAGE_METRICS_KEY = "ERDirectToWeb_displayDetailedPageMetrics"; public final static Logger debugLog = Logger.getLogger("er.directtoweb.ERD2WDebugEnabled"); public final static Logger componentNameLog = Logger.getLogger("er.directtoweb.ERD2WDebugEnabled.componentName"); public final static Logger propertyKeyLog = Logger.getLogger("er.directtoweb.ERD2WDebugEnabled.propertyKey"); public final static NSSelector D2WCONTEXT_SELECTOR = new NSSelector("d2wContext"); static { setUpFrameworkPrincipalClass (ERDirectToWeb.class); } @Override public void finishInitialization() { fixClasses(); ERD2WModel model=ERD2WModel.erDefaultModel(); // force initialization // NOTE: doing Class.ERD2WModel doesn't seem enough // to guarantee fire of ERD2WModel's static initializer // Configures the system for trace rule firing. if(!(D2W.factory() instanceof ERD2WFactory)) { D2W.setFactory(new ERD2WFactory()); } configureTraceRuleFiringRapidTurnAround(); ERDirectToWeb.warmUpRuleCache(); model.checkRules(); NSNotificationCenter.defaultCenter().addObserver(this, new NSSelector("resetModel", ERXConstant.NotificationClassArray), ERXLocalizer.LocalizationDidResetNotification, null); NSNotificationCenter.defaultCenter().addObserver(this, new NSSelector("sortRules", ERXConstant.NotificationClassArray), ERD2WModel.WillSortRules, null); } private void fixClasses(String oldName, String newName) { NSArray<String> names = NSBundle.bundleForClass(getClass()).bundleClassNames(); for (String name : names) { if(name.startsWith(newName)) { Class clazz = ERXPatcher.classForName(name); name = name.replaceFirst(newName + "(\\.[a-z]+)?", oldName); ERXPatcher.setClassForName(clazz, name); } } } private void fixClasses() { fixClasses("er.directtoweb", "er.directtoweb.assignments"); fixClasses("er.directtoweb", "er.directtoweb.assignments.delayed"); fixClasses("er.directtoweb", "er.directtoweb.assignments.defaults"); } public void resetModel(NSNotification n) { ERD2WModel.erDefaultModel().resetModel(); } public void sortRules(NSNotification n) { ERD2WModel model = (ERD2WModel)n.object(); if(ERD2WModel.erDefaultModel() == model) { URL url = ERXFileUtilities.pathURLForResourceNamed("d2wClient.d2wmodel", "ERDirectToWeb", null); model.mergePathURL(url); } } public static void setD2wDebuggingEnabled(WOSession s, boolean enabled) { ERXExtensions.setBooleanFlagOnSessionForKey(s, D2WDEBUGGING_ENABLED_KEY, enabled); if (!enabled) { setD2wComponentNameDebuggingEnabled(s, false); setD2wPropertyKeyDebuggingEnabled(s, false); } } public static boolean d2wDebuggingEnabled(WOSession s) { return ERXExtensions.booleanFlagOnSessionForKeyWithDefault(s, D2WDEBUGGING_ENABLED_KEY, debugLog.isDebugEnabled()); } public static void setD2wComponentNameDebuggingEnabled(WOSession s, boolean enabled) { ERXExtensions.setBooleanFlagOnSessionForKey(s, D2WDISPLAY_COMPONENTNAMES_KEY, enabled); } public static boolean d2wComponentNameDebuggingEnabled(WOSession s) { return ERXExtensions.booleanFlagOnSessionForKeyWithDefault(s, D2WDISPLAY_COMPONENTNAMES_KEY, componentNameLog.isDebugEnabled()); } public static void setD2wPropertyKeyDebuggingEnabled(WOSession s, boolean enabled) { ERXExtensions.setBooleanFlagOnSessionForKey(s, D2WDISPLAY_PROPERTYKEYS_KEY, enabled); } public static boolean d2wPropertyKeyDebuggingEnabled(WOSession s) { return ERXExtensions.booleanFlagOnSessionForKeyWithDefault(s, D2WDISPLAY_PROPERTYKEYS_KEY, propertyKeyLog.isDebugEnabled()); } public static boolean pageMetricsEnabled() { return ERXExtensions.booleanFlagOnSessionForKeyWithDefault(ERXSession.session(), D2WDISPLAY_PAGE_METRICS_KEY, false); } public static void setPageMetricsEnabled(boolean value) { ERXExtensions.setBooleanFlagOnSessionForKey(ERXSession.session(), D2WDISPLAY_PAGE_METRICS_KEY, value); } public static boolean detailedPageMetricsEnabled() { return ERXExtensions.booleanFlagOnSessionForKeyWithDefault(ERXSession.session(), D2WDISPLAY_DETAILED_PAGE_METRICS_KEY, false); } public static void setDetailedPageMetricsEnabled(boolean value) { ERXExtensions.setBooleanFlagOnSessionForKey(ERXSession.session(), D2WDISPLAY_DETAILED_PAGE_METRICS_KEY, value); } public static String resolveUnit(String userInfoUnitString, EOEnterpriseObject object, String prefixKeyPath) { // allows units stored in the user info of the attribute to take the form of @project.unit // this method resolves the @keyPath.. if(userInfoUnitString!=null && userInfoUnitString.indexOf("@")>-1){ String keyPath = userInfoUnitString.substring(1); String PropertyKeyWithoutLastProperty = KeyValuePath.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; } /** * Checks if a given property key is in the format (foo) or [foo] and returns the stripped string. * * @param s the String to convert * @param start the start char * @param end the end char to check for * * @return stripped String or null if the string does not start with <code>start</code> and ends with <code>end</code>. */ public static String convertedPropertyKeyFromString(String s, char start, char end) { if(s.length()> 2) { if(s.charAt(0) == start && s.charAt(s.length()-1) == end) { return s.substring(1, s.length() - 1); } } return null; } /** * Converts a given array of keys to a format usable for section and tab display. * For example ("(foo)", bar, baz) is transformed to a list of ERD2WContainers usable for section display. * The format ((foo, bar, baz)) is also understood. * @param keyArray the NSArray to convert * @param startChar the start char * @param endChar the end char to check for * * @return nested NSMutableArray. */ public static NSMutableArray convertedPropertyKeyArray(NSArray keyArray, char startChar, char endChar) { NSMutableArray result = new NSMutableArray(); Object firstValue = null; if(keyArray.count() > 0) { firstValue = keyArray.objectAtIndex(0); } if(firstValue != null) { boolean isKeyArrayFormat = false; if(firstValue instanceof String) isKeyArrayFormat = convertedPropertyKeyFromString((String)firstValue, startChar, endChar) != null; if(firstValue instanceof String && !isKeyArrayFormat) { ERD2WContainer c=new ERD2WContainer(); c.name = ""; c.keys = new NSMutableArray(keyArray); result.addObject(c); } else { NSMutableArray tmp = null; for (Enumeration e = keyArray.objectEnumerator(); e.hasMoreElements();) { if(isKeyArrayFormat) { String currentValue = (String)e.nextElement(); String currentLabel = convertedPropertyKeyFromString(currentValue, startChar, endChar); if(currentLabel != null) { ERD2WContainer c=new ERD2WContainer(); c.name = currentLabel; tmp = new NSMutableArray(); c.keys = tmp; result.addObject(c); } else { tmp.addObject(currentValue); } } else { NSArray current = (NSArray)e.nextElement(); ERD2WContainer c=new ERD2WContainer(); c.name = (String)current.objectAtIndex(0); c.keys = current.mutableClone(); c.keys.removeObjectAtIndex(0); result.addObject(c); } } } } return result; } /** * Returns a valid sort ordering based on the <code>defaultSortOrdering</code> key. * @param d2wContext */ public static NSArray<EOSortOrdering> sortOrderings(D2WContext d2wContext) { NSMutableArray<EOSortOrdering> validatedSortOrderings = new NSMutableArray<>(); NSArray<String> sortOrderingDefinition = (NSArray<String>) d2wContext.valueForKey("defaultSortOrdering"); if (sortOrderingDefinition != null) { for (int i = 0; i < sortOrderingDefinition.count();) { String sortKey = sortOrderingDefinition.objectAtIndex(i++); String sortSelectorKey = sortOrderingDefinition.objectAtIndex(i++); EOSortOrdering sortOrdering = new EOSortOrdering(sortKey, ERXArrayUtilities.sortSelectorWithKey(sortSelectorKey)); validatedSortOrderings.addObject(sortOrdering); } if (log.isDebugEnabled()) { log.debug("Found sort Orderings in rules " + validatedSortOrderings); } } return validatedSortOrderings; } // This defaults to true. public static boolean booleanForKey(D2WContext context, String key) { return ERXValueUtilities.booleanValue(context.valueForKey(key)); } /** * Subclass of NSForwardException that can hold a d2wContext. Useful when the exception * occurs while evaluating embedded components. The page's d2wContext will not show you the error. * * @author ak */ public static class D2WException extends NSForwardException { /** * 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; private D2WContext _context; public D2WException(Exception ex, D2WContext context) { super(ex); _context = context; } public D2WContext d2wContext() { return _context; } } /** * Logs some debugging info and and throws a D2WException that wraps the original exception. * This is useful when your app fails very deep inside of a repetition of switch components * and you need to find out just what the state of the D2WContext is. * @param ex * @param d2wContext * @d2wKey componentName * @d2wKey customComponentName */ public static synchronized void reportException(Exception ex, D2WContext d2wContext) { if(d2wContext != null) { log.error("Exception <"+ex+">: "+ "pageConfiguration <" + d2wContext.valueForKeyPath("pageConfiguration") + ">, "+ "propertyKey <" + d2wContext.propertyKey() + ">, "+ "entityName <" + d2wContext.valueForKeyPath("entity.name") + ">, "+ "displayPropertyKeys <" +d2wContext.valueForKeyPath("displayPropertyKeys")+ ">, "+ "componentName <" + d2wContext().valueForKey("componentName") + ">, "+ "customComponent <" + d2wContext().valueForKey("customComponentName") + ">", ex); } else { log.error("Exception <"+ex+">: with NULL d2wContext"/*, ex*/); } if(shouldRaiseException(true)) { if(!(ex instanceof D2WException)) { ex = new D2WException(ex, d2wContext); } throw (D2WException)ex; } } /** * Gathers D2W-related information from the current context. This is mainly useful for debugging. * @param context the current context * @return a dictionary of D2W-related keys to describe the D2W state of the context. */ public static synchronized NSMutableDictionary informationForContext(WOContext context) { NSMutableDictionary info = new NSMutableDictionary(); D2WContext d2wContext = null; NSArray componentStack = ERXWOContext._componentPath(context); // Try to get the information for the D2WPage closest to the end of the component stack, i.e., more specific // info., that is especially helpful for finding problems in embedded page configurations. WOComponent component = null; for (Enumeration componentsEnum = componentStack.reverseObjectEnumerator(); componentsEnum.hasMoreElements();) { WOComponent c = (WOComponent)componentsEnum.nextElement(); if (c instanceof D2WPage) { component = c; break; } } if (null == component) { // Fall back to the highest level page. component = context.page(); } try { d2wContext = (D2WContext)component.valueForKey("d2wContext"); } catch (NSKeyValueCoding.UnknownKeyException uke) { if (log.isInfoEnabled()) { log.info("Could not retrieve D2WContext from component context; it is probably not a D2W component."); } } if (d2wContext != null) { NSMutableDictionary d2wInfo = informationForD2WContext(d2wContext); if (component instanceof ERD2WPage) { ERD2WPage currentPage = (ERD2WPage)component; String subTask = (String)d2wContext.valueForKey("subTask"); if ("tab".equals(subTask) || "wizard".equals("subTask")) { NSArray sections = currentPage.sectionsForCurrentTab(); d2wInfo.setObjectForKey(sections != null ? sections : "null", "D2W-SectionsContentsForCurrentTab"); d2wInfo.removeObjectForKey("D2W-TabSectionsContents"); } } info.addEntriesFromDictionary(d2wInfo); } return info; } /** * Gathers D2W-related information from the current context. This is mainly useful for debugging. * @param d2wContext the D2W context from which to derive the debugging information * @return a dictionary of D2W-related keys to describe the state of the provided D2W context. */ public static synchronized NSMutableDictionary informationForD2WContext(D2WContext d2wContext) { NSMutableDictionary info = new NSMutableDictionary(); if (d2wContext != null) { String pageConfiguration = (String)d2wContext.valueForKeyPath("pageConfiguration"); info.setObjectForKey(pageConfiguration != null ? pageConfiguration : "null", "D2W-PageConfiguration"); String propertyKey = d2wContext.propertyKey(); info.setObjectForKey(propertyKey != null ? propertyKey : "null", "D2W-PropertyKey"); String entityName = (String)d2wContext.valueForKeyPath("entity.name"); info.setObjectForKey(entityName != null ? entityName : "null", "D2W-EntityName"); String task = (String)d2wContext.valueForKey("task"); info.setObjectForKey(task != null ? task : "null", "D2W-SubTask"); String subTask = (String)d2wContext.valueForKey("subTask"); info.setObjectForKey(subTask != null ? subTask : "null", "D2W-SubTask"); if ("tab".equals(subTask) || "wizard".equals("subTask")) { String tabKey = (String)d2wContext.valueForKey("tabKey"); info.setObjectForKey(tabKey != null ? tabKey : "null", "D2W-TabKey"); NSArray tabSections = (NSArray)d2wContext.valueForKey("tabSectionsContents"); info.setObjectForKey(tabSections != null ? tabSections : "null", "D2W-TabSectionsContents"); } else { NSArray displayPropertyKeys = (NSArray)d2wContext.valueForKey("displayPropertyKeys"); info.setObjectForKey(displayPropertyKeys != null ? displayPropertyKeys : "null", "D2W-DisplayPropertyKeys"); } String componentName = (String)d2wContext.valueForKey("componentName"); info.setObjectForKey(componentName != null ? componentName : "null", "D2W-ComponentName"); if (componentName != null && componentName.indexOf("CustomComponent") > 0) { String customComponentName = (String)d2wContext.valueForKey("customComponentName"); info.setObjectForKey(customComponentName != null ? customComponentName : "null", "D2W-ComponentName"); } } return info; } /** * Checks the system property <code>er.directtoweb.ERDirectToWeb.shouldRaiseExceptions</code>. * @param defaultValue */ public static boolean shouldRaiseException(boolean defaultValue) { return ERXProperties.booleanForKeyWithDefault("er.directtoweb.ERDirectToWeb.shouldRaiseExceptions", defaultValue); } public static synchronized String displayNameForPropertyKey(String key, String entityName) { EOEntity entity = EOModelGroup.defaultGroup().entityNamed(entityName); d2wContext()._localValues().clear(); //ERD2WUtilities.resetContextCache(d2wContext()); d2wContext().setEntity(entity); d2wContext().setPropertyKey(key); String result = d2wContext().displayNameForProperty(); d2wContext()._localValues().clear(); return result; } // Needs to be a late init because then it will hook itself up to the correct D2WModel private static D2WContext _context; private static D2WContext d2wContext() { if (_context == null) _context = ERD2WContext.newContext(); return _context; } public static Object d2wContextValueForKey(String key, String entityName) { return d2wContextValueForKey(key, entityName, null); } public static synchronized Object d2wContextValueForKey(String key, String entityName, NSDictionary extraValuesForContext) { EOEntity entity = EOModelGroup.defaultGroup().entityNamed(entityName); d2wContext()._localValues().clear(); // ERD2WUtilities.resetContextCache(d2wContext()); d2wContext().setEntity(entity); if (extraValuesForContext!=null) { d2wContext().takeValuesFromDictionary(extraValuesForContext); /* for (Enumeration e=extraValuesForContext.allKeys().objectEnumerator(); e.hasMoreElements();) { String k=(String)e.nextElement(); d2wContext().takeValueForKey(extraValuesForContext.objectForKey(k),k); }*/ } Object result = d2wContext().valueForKey(key); d2wContext()._localValues().clear(); return result; } public static String createConfigurationForEntityNamed(String entityName) { return (String)d2wContextValueForKey("createConfigurationName", entityName); } public static void warmUpRuleCache() { log.debug("Preparing DirectToWeb Data Structures"); ERD2WModel.erDefaultModel().prepareDataStructures(); } public static Logger trace; public void configureTraceRuleFiring(NSNotification n) { ERDirectToWeb.configureTraceRuleFiring(); } private void configureTraceRuleFiringRapidTurnAround() { if (trace == null) { // otherwise not properly initialized trace = Logger.getLogger("er.directtoweb.rules.D2WTraceRuleFiringEnabled"); // Note: If the configuration file says debug, but the command line parameter doesn't we need to turn // rule tracing on. // BOOGIE configureTraceRuleFiring(); NSNotificationCenter.defaultCenter().addObserver(ERXFrameworkPrincipal.sharedInstance(ERDirectToWeb.class), new NSSelector("configureTraceRuleFiring", ERXConstant.NotificationClassArray), ERXConfigurationManager.ConfigurationDidChangeNotification, null); } } // This is the actual method that turns trace rule firing on and off. public static void configureTraceRuleFiring() { //AK: we can trace firing much more fine-grained than the default engine // and also enabling the debug level NSLog spews out a ton of ridiculous // info about images and the like, so we leave the NSLog alone... if (trace.isDebugEnabled() && !NSLog.debugLoggingAllowedForGroups(NSLog.DebugGroupRules)) { //NSLog.allowDebugLoggingForGroups(NSLog.DebugGroupRules); //NSLog.setAllowedDebugLevel(NSLog.DebugLevelDetailed); trace.info("Rule tracing on"); } else if (!trace.isDebugEnabled() && NSLog.debugLoggingAllowedForGroups(NSLog.DebugGroupRules)) { //NSLog.refuseDebugLoggingForGroups(NSLog.DebugGroupRules); trace.info("Rule tracing off"); } } public static NSArray displayableArrayForKeyPathArray(NSArray array, String entityForReportName){ if(array == null) { return null; } NSMutableArray result = new NSMutableArray(); for(Enumeration e = array.objectEnumerator(); e.hasMoreElements(); ){ String key = (String)e.nextElement(); result.addObject(new ERXKeyValuePair(key, ERDirectToWeb.displayNameForPropertyKey(key, entityForReportName))); } return result; } }