hidden treasures in project wonder
TRANSCRIPT
Spirit of Mike SchragWhereabouts Unknown
Channeled By Chuck HillGlobal Village Consulting
Hidden Treasures in Project Wonder
What You See
• Mike’s Step 1: Look at every damn method in Wonder.
• Mike’s Step 1a: Seriously. Every one.
• Mike’s Step 2: “Cool” = Chosen
What You Get
• Notification of existence
• Nothing in-depth
Type-safe pageWithName
• <T extends WOComponent> T pageWithName(Class<T> componentClass)
• ERXApplication, ERXDirectAction, ERXComponent
• less typing
• refactoring updates them
• compiler errors for speeling errors
Type-safe pageWithNameold wayMyPage page = (MyPage)pageWithName(“MyPage”);
new wayMyPage page = (MyPage)pageWithName(MyPage.class.getName());
cool wayMyPage page = pageWithName(MyPage.class);
IVersionManager• better control of static resource caching
• infinite expiration in Apache
• change resource url when resource changes
• pluggable versioning schemes
• “default” = WebObjects default
• “properties” = specify version numbers in properties
• your own
PropertiesVersionManagerpropertieser.extensions.ERXResourceManager.versionManager=propertieser.extensions.ERXResourceManager.versionManager.default=10er.extensions.ERXResourceManager.versionManager.bundleName.resourceName=20
produces/WebObjects/YourApp.woa/WebServerResources/awesomeImage.png?10/WebObjects/bundleName/WebServerResources/resourceName?20
ERXDirectAction / log4jurl/wa/ERXDirectAction/log4j?pw=somepw
propertieser.extensions.ERXLog4JPassword=somepw
ERXDirectAction / log4j
ERXDirectAction / eoAdaptorDebugging
url/wa/ERXDirectAction/eoAdaptorDebugging?debug=on&pw=somepw/wa/ERXDirectAction/eoAdaptorDebugging?debug=off&pw=somepw
propertieser.extensions.ERXEOAdaptorDebuggingPassword=somepw
ERXMainRunner
• initializes all of Wonder
• runs “main” method inside of an Application context
• java -classpath ... er.extensions.appserver.ERXMainRunner -mainClass YourClass
ERXApplication._rewriteURL
• Pretty URLs
• One generator method to rule them all
ERXApplication._rewriteURL (simple method)
propertieser.extensions.ERXApplication.replaceApplicationPath.pattern= ↵/cgi-bin/WebObjects/YourApp.woa
er.extensions.ERXApplication.replaceApplicationPath.replace= ↵/yourapp
produceshttp://server.com/yourapp/wa/default
apache rewrite ruleRewriteRule ^/yourapp(.*)$ /cgi-bin/WebObjects/YourApp.woa$1 [PT,L]
ERXApplication._rewriteURL (complex method)
Application subclass@Overridepublic String _rewriteURL(String url) { return convertURLWithFancyRegexOrSomething(url);}
apache rewrite rulerewrite rule has to be an inverse of your _rewriteURL implementation
ERXRedirect
• drop-in replacement for WORedirect
• can redirect to a component instance rather than a direct action
• generates document.location.href=... in Ajax request
• https support
• api for setting query parameters
ERXRedirectpublic WOActionResults doSomething() { ERXRedirect redirect = pageWithName(ERXRedirect.class); NextPage nextPage = pageWithName(NextPage.class); nextPage.setState(someStateFromThisPage); redirect.setComponent(nextPage); return redirect;}
ERXSession.useSecureSessionCookies
• secure cookies are only sent across https requests
• wosid and woinst are not secure cookies by default (and thus vulnerable to sniffing off a non-https url)
ERXSession.useSecureSessionCookies
@Overridepublic boolean useSecureSessionCookies() { return true;}
or
er.extensions.ERXSession.useSecureSessionCookies=true
ERXFlickrBatchNavigation• WOBatchNavigationBar looks like ass
• ERXFlickrBatchNavigation does not
• Drop-in replacement of WOBatchNavigationBar
ERXDataHyperlink
• Like WOHyperlink but with object-based attribute passing
• Automagically calls accessor methods on pageName component
• Safe ... doesn’t leak data to user
• An example will make more sense
ERXDataHyperlinkPersonList page<wo:ERXDataHyperlink pageName=”PersonEdit” ↵ person=”$currentPerson”> edit person</wo:ERXDataHyperlink>
PersonEdit pagepublic class PersonEdit extends WOComponent { ... public void setPerson(Person person) { _person = person; }}
ERXInlineTemplate
• Generate a WOComponent on-the-fly
• Generate a WOComponent from a database (i.e. a CMS)
• Take over the world?
ERXInlineTemplatebasic<wo:ERXInlineTemplate html=”$someHtmlString” wod=”$someWodString”/>(using woognl doesn’t require a wod)
cache generated component<wo:ERXInlineTemplate html=”$someHtmlString” wod=”$someWodString” cacheKey=”MyCacheKey” cacheVersion=”$computedVersionObject”/>(when cacheVersion changes, regenerate)
ERXWOComponentContent / ERXWOTemplate
• WOComponentContent but with multiple content areas
• example: a DialogComponent has a “title” area and a “content” area
ERXWOComponentContent / ERXWOTemplate
DialogComponent<div> <div><wo:ERXWOComponentContent templateName=”title”/></div> <div><wo:ERXWOComponentContent templateName=”content”/></div></div>
CallingComponent<wo:DialogComponent> <wo:ERXWOTemplate templateName=”title”> this is the title and you can put real components in here </wo:ERXWOTemplate> <wo:ERXWOTemplate templateName=”content”> this is the content of the dialog </wo:ERXWOTemplate></wo:DialogComponent>
ERXLoremIpsumGenerator
• sometimes you just need fake content (test cases, load tests, mockups)
• “Lorem ipsum dolor sit amet, ...”
• ERXLoremIpsumGenerator.paragraph(3) = 3 paragraphs
• ERXLoremIpsumGenerator.sentence(1) = 1 sentence
• ERXLoremIpsumGenerator.words(10) = 10 words
Captchas and Spam Checks
• ERXSimpleSpamCheck (ERExtensions.framework)sets a hidden field with javascript
• ERReCaptcha (ERCaptcha.framework)reCAPTCHA wrapper (http://www.google.com/recaptcha)
• ERAkismet (ERCaptcha.framework)Akismet wrapper (http://akismet.com); spam checks blog comments
Captchas and Spam Checks
• calls validationFailedWithException on failure
• sets “valid” binding accordingly
Captchas and Spam Checks
<wo:form> <wo:ERXSimpleSpamCheck />
<wo:WOTextField value=”$firstName” /> <wo:WOSubmitButton action=”$doSomething” /></wo:form>
ERXRssPage• quickly produce RSS feeds
• feedTitle, feedUrl, feedDescription
• list : list of items to show in the feed
• item : repetition item
• itemGuid, itemTitle, itemLink, itemPubDate, itemAuthor
• description from WOComponentContent
ERXRssPage<wo:ERXRssPage feedTitle=”RSS of People” feedUrl=”http://yourhost.com” feedDescription=”This is my list of people!” list=”$people” item=”$person” itemGuid=”$person.primaryKey” itemTitle=”$person.fullName” itemLink=”$someURLToThePerson” itemPubDate=”$person.creationDate” />
ERXWORepetition
• WORepetition relies on repetition item indexes
• changing item order = you just clicked “delete” on the wrong row ... whoops
• uses key instead of index
• small risk of collision, but unlikely in tests (use uniqueKey binding to prevent)
ERXWORepetitionproperties# use hash codes of objects instead of indexer.extensions.ERXWORepetition.checkHashCodes=true
# throw an exception if a match isn’t founder.extensions.ERXWORepetition.raiseOnUnmatchedObject=true
unique keys# use item PKs instead of hash codes<wo:ERXWORepetition uniqueKey=”$person.primaryKey”> ...
External Services
• ERXGoogleSpell.correct(String)
• spelling correction like Google’s “did you mean”
• ERXYahooContentAnalysisService.termExtraction(..)
• extracts important terms from text
• useful for guessing ERTaggable tags
• Access keys required, read Javadoc for details
ERXEnterpriseObjectCache
• caches EO’s based on a keypath
• optionally filter with qualifier (dynamically updates!)example: two caches -- published=true, published=false
• optional timed expiration
ERXEnterpriseObjectCache
• use this constructor and shouldRetainObjects=true!
public ERXEnterpriseObjectCache(String entityName, String keyPath, EOQualifier qualifier, long timeout, boolean shouldRetainObjects, boolean shouldFetchInitialValues)
ERXEnterpriseObjectCache
# cache any CMSPage objects by their “name” key # that are published=true# expire them after 60 secondsERXEnterpriseObjectCache cache = new ERXEnterpriseObjectCache( CMSPage.ENTITY_NAME, CMSPage.NAME_KEY, CMSPage.PUBLISHED.isTrue(), 60 * 1000);
EOEditingContext editingContext = ERXEC.newEditingContext();CMSPage homePage = cache.objectForKey(editingContext, “HomePage”);homePage.setPublished(false);editingContext.saveChanges();# published = false, so it is dynamically removed from the cache
ERXEOControlUtilities
• ERXEOControlUtilities.convertEOtoGID / convertGIDtoEOrecursively convert data structures from EO’s to GID’s (NSDictionary of String to NSArray of EO’s becomes NSDictionary of String to NSArray of GID’s, etc) and the corresponding inverse method
ERXEOControlUtilities
• ERXEOControlUtilities.eoEqualscompare two EO’s, returning true if their GID’s match, regardless of the EOEditingContext that contains both of them
• ERXEOControlUtilities.aggregateFunctionWithQualifierperform MIN/MAX/AVG/etc database functions on attributes of an entity without writing custom SQL
Automatic Batch Faulting• Common problem: Fetch array of Person
objects, show them in a table, for each row, show the person.company().name()
• Common solution: Manually batch fetch “company.name” on the Person objects.
• Bad-ass solution:er.extensions.ERXDatabaseContextDelegate.autoBatchFetchSize=50
• Automatically batch fetches faulted relationships on all EO’s that were fetched from the same query
Automatic Batch FaultingNSArray<Person> people = ec.objectsWithFetchSpecification(fspec);
# this automatically faults all the companies for # the person objects in the “people” array (up to # your 50 max specified in the property)Company comp = people.objectAtIndex(0).company();String name = comp.name();
# this automatically faults all the employees for # all the companies that were just batch fetched # above -- it’s magic and recursiveNSArray<Person> employees = comp.employees();
# restricts to only the visible objects if attached # to a display group
ERXUnitAwareDecimalFormat
• automatically format bytes/meters/grams/seconds in metric decimal form
• scales to appropriate unit based on size
• 1234567890 bytes becomes “1.15GB”
NumberFormat formatter = new ERXUnitAwareDecimalFormat(ERXUnitAwareDecimalFormat.BYTE);formatter.setMaximumFractionDigits(2);formatter.format(1234567890.0);
ERXJDBCAdaptor
• fixes double connection bug from jdbcInfo
• should just be the default
• er.extensions.ERXJDBCAdaptor.className = er.extensions.jdbc.ERXJDBCAdaptor
ERXMutableURL
• java.net.URL is garbage. ERXMutableURL is not.
• mutable interface to a URL object
• add query parameters one-by-one
• supports weird “urls” like “javascript:” and incomplete “/wa/something”
ERXMutableURLERXMutableURL url = new ERXMutableURL( "http://yourhost.com:80/tutorial/index.html?key1=value1#someref");url.setRef(null);url.removeQueryParameter("key1");url.addQueryParameter("key2", “value2”);url.addQueryParameters(“key3=value3&key4=value4”);
java.net.URL javaneturl = url.toURL();String urlString = url.toExternalForm();
ERXThreadStorage
• a ThreadLocal dictionary that resets for each request
• Object obj = ERXThreadStorage.valueForKey(String)
• ERXThreadStorage.takeValueForKey(Object, String)
• common uses: current user, current session, current context
ERXValueUtilities• convert objects to various common types
including “default” form
• example:booleanValue(Object) / booleanValueWithDefault(Object, boolean def)
• supports boolean, Boolean, int, Integer, float, Float, double, Double, long, Long, array, set, dictionary, data, bigDecimal, enum
• converts from every possible form for you -- handy for bindings
ERMemoryAdaptor
• acts like a database, but just talking to dictionaries
• great for test cases
• no SQL support -- don’t try to get tricky
• Include JavaMemoryAdaptor.framework
• dbConnectAdaptorGLOBAL=Memoryor[ModelName].adaptor=Memory
ERPDFWrapper
• Turn any HTML+CSS page into a PDF
• Include ERPDFGeneration.framework
• <wo:ERPDFWrapper><html> ...</wo:ERPDFWrapper>
• Done. That’s just cool.
PFProfiler / SESnapshotExplorer
• These are in another talk.
• They’re cool, though.
• Pay attention during that talk.
ERXResponseRewriter.Delegate
• intercept all resource insertions, optionally replacing (or denying) them
• example:you have your own prototype.js and you want to replace Ajax.framework’s version with yours
• via properties (for simple cases) or via custom delegate
• responseRewriterWillAddResource
ERXResponseRewriter.Delegate
propertieser.extensions.ERXResponseRewriter.resource.[framework].[filename]=[newFramework].[newFileName]er.extensions.ERXResponseRewriter.resource.Ajax.prototype.js= app.myprototype.js
delegate (example replaces EVERY file with myfile.png -- be smarter)ERXResponseRewriter.setDelegate(new ERXResponseRewriter.Delegate() { public boolean responseRewriterShouldAddResource( String framework, String fileName) { return true; }
public ERXResponseRewriter.Resource responseRewriterWillAddResource( String framework, String fileName) { return new ERXResponseRewriter.Resource(“app”, “myfile.png”);});
applyRestrictingQualifierOnInsert
• er.extensions.ERXEnterpriseObject.applyRestrictingQualifierOnInsert=true
• makes new objects automatically match their entity’s restricting qualifier
• i.e. if Employee is “personType = 5”, a new Employee will automatically have personType = 5 set on it
• no more awakeFromInsertion junk
secureDisabled
• You use DirectConnect
• You don’t have SSL in dev
• You want to run your app
• er.extensions.ERXRequest.secureDisabled=true
• Turns off all https URL generation -- only works in dev
ERXAdaptorChannelDelegate
• conditionally log slow SQL execution (and some other cool debugging stuff)
• took 100ms, log.info the expression
• took 1000ms, log.warn the expression
• etc ...
ERXAdaptorChannelDelegate
propertieser.extensions.ERXAdaptorChannelDelegate.enabled=trueer.extensions.ERXAdaptorChannelDelegate.trace.milliSeconds.debug=0er.extensions.ERXAdaptorChannelDelegate.trace.milliSeconds.info=100er.extensions.ERXAdaptorChannelDelegate.trace.milliSeconds.warn=1000er.extensions.ERXAdaptorChannelDelegate.trace.milliSeconds.error=5000
Q&ASpirit of Mike SchragWhereabouts Unknown
Channeled By Chuck HillGlobal Village Consulting