Skip to content

ColdSpring AOP Bean Support #46

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 29 additions & 8 deletions core/api.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
</cfif>
<!--- allow reloading --->
<cfif structKeyExists(url, application._taffy.settings.reloadKey) and url[application._taffy.settings.reloadKey] eq application._taffy.settings.reloadPassword>
<cfset applicationStartEvent() />
<cfset setupFramework() />
</cfif>
<!--- allow pass-thru for selected paths --->
Expand Down Expand Up @@ -216,22 +217,22 @@
<cffunction name="parseRequest" access="private" output="false" returnType="struct">
<cfset var requestObj = {} />
<cfset var tmp = 0 />

<!--- Check for method tunnelling by clients unable to send PUT/DELETE requests (e.g. Flash Player);
Actual desired method will be contained in a special header --->
<cfset var httpMethodOverride = GetPageContext().getRequest().getHeader("X-HTTP-Method-Override") />

<!--- attempt to find the cfc for the requested uri --->
<cfset requestObj.matchingRegex = matchURI(getPath()) />

<!--- uri doesn't map to any known resources --->
<cfif not len(requestObj.matchingRegex)>
<cfset throwError(404, "Not Found") />
</cfif>

<!--- get the cfc name and token array for the matching regex --->
<cfset requestObj.matchDetails = application._taffy.endpoints[requestObj.matchingRegex] />

<!--- which verb is requested? --->
<cfset requestObj.verb = cgi.request_method />

Expand Down Expand Up @@ -407,6 +408,15 @@
<cfelseif structKeyExists(local.cfcMetadata, "taffy:uri")>
<cfset local.uri = local.cfcMetadata["taffy:uri"] />
</cfif>

<cfif structKeyExists(local.cfcMetaData, "taffy:aopbean")>
<cfset local.cachedBeanName = local.cfcMetaData["taffy:aopbean"] />
<cfelseif structKeyExists(local.cfcMetaData, "taffy_aopbean")>
<cfset local.cachedBeanName = local.cfcMetaData["taffy_aopbean"] />
<cfelse>
<cfset local.cachedBeanName = local.beanName />
</cfif>

<!--- if it doesn't have a uri, then it's not a resource --->
<cfif len(local.uri)>
<cfset local.metaInfo = convertURItoRegex(local.uri) />
Expand All @@ -417,7 +427,7 @@
errorcode="taffy.resources.DuplicateUriPattern"
/>
</cfif>
<cfset application._taffy.endpoints[local.metaInfo.uriRegex] = { beanName = local.beanName, tokens = local.metaInfo.tokens, methods = structNew(), srcURI = local.uri } />
<cfset application._taffy.endpoints[local.metaInfo.uriRegex] = { beanName = local.cachedBeanName, tokens = local.metaInfo.tokens, methods = structNew(), srcURI = local.uri } />
<cfloop array="#local.cfcMetadata.functions#" index="local.f">
<cfif local.f.name eq "get" or local.f.name eq "post" or local.f.name eq "put" or local.f.name eq "delete">
<cfset application._taffy.endpoints[local.metaInfo.uriRegex].methods[local.f.name] = local.f.name />
Expand Down Expand Up @@ -467,7 +477,6 @@
<cfset var local = StructNew() />
<cfset local.beans = application._taffy.externalBeanFactory.getBeanDefinitionList() />
<cfset local.beanList = "" />

<cfloop collection="#local.beans#" item="local.beanName">
<!---
Can't call instanceOf() method on beans generated by factories; there is no
Expand All @@ -476,8 +485,10 @@
return type metadata on the factory method or find some other way to determine if
this is a Taffy bean.
--->
<cfif local.beans[local.beanName].getBeanClass() NEQ "" and local.beans[local.beanName].instanceOf('taffy.core.resource')>
<cfset local.beanList = listAppend(local.beanList, local.beanName) />
<cfif (local.beans[local.beanName].getBeanClass() NEQ "")>
<cfif (local.beans[local.beanName].instanceOf('taffy.core.resource'))>
<cfset local.beanList = listAppend(local.beanList, local.beanName) />
</cfif>
</cfif>
</cfloop>
<cfreturn local.beanList />
Expand Down Expand Up @@ -568,6 +579,7 @@
</cfif>
<cfset cacheBeanMetaData(application._taffy.externalBeanFactory, arguments.beanList) />
</cffunction>

<cffunction name="getBeanFactory" access="public" output="false">
<cfreturn application._taffy.factory />
</cffunction>
Expand Down Expand Up @@ -613,6 +625,15 @@
<cfset application._taffy.settings.mimeExtensions[arguments.extension] = arguments.mimeType />
<cfset application._taffy.settings.mimeTypes[arguments.mimeType] = arguments.extension />
</cffunction>

<cffunction name="registerExtensionRepresentation" access="public" output="false" returntype="void">
<cfargument name="extensionList" type="string" required="true" hint="ex: json" />
<cfargument name="representation" type="string" required="true" hint="ex: cfc.SomeRep" />
<cfloop from="1" to="#ListLen(arguments.extensionList)#" index="i">
<cfset application._taffy.settings.extensionRepresentations["#ListGetAt(arguments.extensionList,i)#"] = arguments.representation />
</cfloop>
</cffunction>


<cffunction name="setDefaultRepresentationClass" access="public" output="false" returnType="void" hint="Override the global default representation object with a custom class">
<cfargument name="customClassDotPath" type="string" required="true" hint="Dot-notation path to your custom class to use as the default" />
Expand Down
2 changes: 1 addition & 1 deletion core/resource.cfc
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<cfcomponent hint="base class for taffy REST components">

<!--- helper functions --->
<cffunction name="representationOf" access="private" output="false" hint="returns an object capable of serializing the data in a variety of formats">
<cffunction name="representationOf" access="public" output="false" hint="returns an object capable of serializing the data in a variety of formats">
<cfargument name="data" required="true" hint="any simple or complex data that should be returned for the request" />
<cfargument name="customRepresentationClass" type="string" required="false" default="" hint="pass in the dot.notation.cfc.path for your custom representation object" />

Expand Down
40 changes: 40 additions & 0 deletions examples/coldspring_aop/Application.cfc
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<cfcomponent extends="taffy.core.api">
<cfscript>
this.name = hash(getCurrentTemplatePath());
this.applicationTimeout = createTimeSpan(0,0,0,1);
this.mappings = structNew();
//this.mappings["/coldspring"] = ExpandPath("{your-path-here}");

// do your onApplicationStart stuff here
function applicationStartEvent(){
initColdSpring();
}

// do your onRequestStart stuff here
function requestStartEvent(){}

// this function is called after the request has been parsed and all request details are known
function onTaffyRequest(verb, cfc, requestArguments, mimeExt){
// this would be a good place for you to check API key validity and other non-resource-specific validation
return true;
}

// called when taffy is initializing or when a reload is requested
function configureTaffy(){
setDebugKey("debug");
setReloadKey("reload");
setReloadPassword("true");
setBeanFactory(application.oBeanFactory);
// Usage of this function is entirely optional. You may omit it if you want to use the default representation class.
// Change this to a custom class to change the default for the entire API instead of overriding for every individual response.
setDefaultRepresentationClass("taffy.core.genericRepresentation");
}


function initColdSpring() {
application.oBeanFactory = CreateObject("component","coldspring.beans.DefaultXmlBeanFactory").init();
application.oBeanFactory.loadBeansFromXMLFile("/config/coldspring.xml");
}

</cfscript>
</cfcomponent>
193 changes: 193 additions & 0 deletions examples/coldspring_aop/com/aspects/CachingAroundAdvice.cfc
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
<cfcomponent displayname="ReturnFormatter" extends="coldspring.aop.MethodInterceptor" hint="" output="false">

<cffunction name="invokeMethod" access="public" returntype="any">
<cfargument name="methodInvocation" type="coldspring.aop.MethodInvocation" required="false" />
<cfscript>

// Look at the cfc to determine if it's configured for caching
local.stRequestMeta = getRequestMeta(
metaData = GetMetaData(arguments.methodInvocation.getTarget()),
verb = arguments.methodInvocation.getMethod().getMethodName(),
args = arguments.methodInvocation.getArguments()
);

// If we were able to retrieve data from cache, simply return that data
if (local.stRequestMeta["foundInCache"]) {
return local.stRequestMeta["cacheData"];
}
else {

// If data was not in cache, run the method.
local.methodReturnData = arguments.methodInvocation.proceed();

// Put the return data into cache.
if (IsDefined("local.methodReturnData")) {
cachePut(local.stRequestMeta["cacheKey"], local.methodReturnData, local.stRequestMeta["timeSpan"]);
return local.methodReturnData;
}
}
</cfscript>
</cffunction>



<cffunction name="getRequestMeta" access="private" returntype="struct">
<cfargument name="metaData" type="struct" required="true" />
<cfargument name="verb" type="string" required="true" />
<cfargument name="args" type="struct" required="true" />
<cfscript>

// Default variables
local.stCacheSettings = StructNew();
local.stCacheSettings["toCache"] = false;
local.stCacheSettings["cacheKey"] = "";
local.stCacheSettings["cacheTimeout"] = 0;
local.stCacheSettings["foundInCache"] = false;
local.stCacheSettings["cacheData"] = "";
local.stCacheSettings["timeSpan"] = 0;
local.stCacheSettings["requestURI"] = rebuildURI(
taffyURI = arguments.metaData["taffy:uri"],
args = arguments.args
);

// Using the meta data, determine the component caching defaults
if (StructKeyExists(arguments.metaData,"taffy:cache")) {
local.stCacheSettings["toCache"] = arguments.metaData["taffy:cache"];
}
else if (StructKeyExists(arguments.metaData,"taffy_cache")) {
local.stCacheSettings["toCache"] = arguments.metaData["taffy_cache"];
}

if (StructKeyExists(arguments.metaData,"taffy:cachetimeout")) {
local.stCacheSettings["cacheTimeout"] = arguments.metaData["taffy:cachetimeout"];
}
else if (StructKeyExists(arguments.metaData,"taffy_cachetimeout")) {
local.stCacheSettings["cacheTimeout"] = arguments.metaData["taffy:cachetimeout"];
}

if (StructKeyExists(arguments.metaData,"taffy:cacheunit")) {
local.stCacheSettings["unit"] = arguments.metaData["taffy:cacheunit"];
}
else if (StructKeyExists(arguments.metaData,"taffy_cacheunit")) {
local.stCacheSettings["unit"] = arguments.metaData["taffy:_acheunit"];
}
else {
local.stCacheSettings["unit"] = "minutes";
}

// Now we must look at the method level. First we need to loop over the functions
// array and find our method.
local.methods = arguments.metaData["functions"];
for (local.i = 1; local.i LTE ArrayLen(local.methods); local.i++) {
if (local.methods[i].name EQ arguments.verb) {
local.activeMethod = local.methods[i];
break;
}
}

// Now that we know the method, look at its meta data to see if there are
// overriding cache settings on the method level.
if (StructKeyExists(local.activeMethod,"taffy:cache")) {
local.stCacheSettings["toCache"] = local.activeMethod['taffy:cache'];
}
else if (StructKeyExists(local.activeMethod,"taffy_cache")) {
local.stCacheSettings["toCache"] = local.activeMethod['taffy_cache'];
}

if (StructKeyExists(local.activeMethod,"taffy:cachetimeout")) {
local.stCacheSettings["cacheTimeout"] = local.activeMethod["taffy:cachetimeout"];
}
else if (StructKeyExists(local.activeMethod,"taffy_cachetimeout")) {
local.stCacheSettings["cacheTimeout"] = local.activeMethod["taffy_cachetimeout"];
}

if (StructKeyExists(local.activeMethod,"taffy:cacheunit")) {
local.stCacheSettings["unit"] = local.activeMethod["taffy:cacheunit"];
}
else if (StructKeyExists(local.activeMethod,"taffy_cacheunit")) {
local.stCacheSettings["unit"] = local.activeMethod["taffy_cacheunit"];
}

// Determine if we're doing any caching at all
if (local.stCacheSettings["toCache"]) {

// We are caching. Next step, build the unique cache key that represents
// this specific request.
local.stCacheSettings["cacheKey"] = buildRequestCacheKey(
resource = arguments.metaData.fullname,
verb = "get",
args = arguments.args
);

// Look to see if the data is available in cache
local.cacheData = cacheGet(local.stCacheSettings["cacheKey"]);
if (IsDefined("local.cacheData")) {
local.cacheMeta = CacheGetMetaData(local.stCacheSettings["cacheKey"]);
local.stCacheSettings["foundInCache"] = true;
local.stCacheSettings["cacheData"] = local.cacheData;
local.stCacheSettings["cachedAt"] = local.cacheMeta["createdtime"];
}

}

// Create the appropriate timespan
if (local.stCacheSettings["unit"] EQ "days") {
local.stCacheSettings["timeSpan"] = CreateTimeSpan(local.stCacheSettings["cacheTimeout"],0,0,0);
}
else if (local.stCacheSettings["unit"] EQ "hours") {
local.stCacheSettings["timeSpan"] = CreateTimeSpan(0,local.stCacheSettings["cacheTimeout"],0,0);
}
else if (local.stCacheSettings["unit"] EQ "minutes") {
local.stCacheSettings["timeSpan"] = CreateTimeSpan(0,0,local.stCacheSettings["cacheTimeout"],0);
}
else if (local.stCacheSettings["unit"] EQ "seconds") {
local.stCacheSettings["timeSpan"] = CreateTimeSpan(0,0,0,local.stCacheSettings["cacheTimeout"]);
}

return local.stCacheSettings;
</cfscript>
</cffunction>


<cffunction name="buildRequestCacheKey" access="private" output="no" returntype="string" hint="Builds a unique key by identifying the unique parts of a resource request.">
<cfargument name="resource" type="string" required="true" />
<cfargument name="verb" type="string" required="true" />
<cfargument name="args" type="any" required="true" />
<cfset local.key = arguments.resource & "_" & arguments.verb />
<cfloop collection="#arguments.args#" item="local.argKey">
<cfset local.key = local.key & "_" & local.argKey & ":" & arguments.args[local.argKey] />
</cfloop>
<cfreturn LCase(local.key) />
</cffunction>


<cffunction name="rebuildURI" access="private" returntype="string" hint="" output="no">
<cfargument name="taffyURI" type="string" required="true" />
<cfargument name="args" type="struct" required="true" />
<cfscript>

local.sRebuiltURL = arguments.taffyURI;
local.nQueryParamCount = 0;

// Loop over the arguments. Add to a pattern, search, replace, or append
for (local.key IN arguments.args) {
local.sPattern = "{#local.key#}";
if (FindNoCase(local.sPattern, local.sRebuiltURL)) {
// The current argument is a pattern match and should be replaced in the pattern format
local.sRebuiltURL = ReplaceNoCase(local.sRebuiltURL, local.sPattern, StructFind(arguments.args, local.key));
}
else {
// This argument was not in the url path, so it needs to be appended to the url
if (local.nQueryParamCount EQ 0) { local.sRebuiltURL &= "?"; }
else { local.sRebuiltURL &= "&"; }
local.sRebuiltURL &= "#local.key#=#StructFind(arguments.args, local.key)#";
local.nQueryParamCount++;
}
}

return LCase(local.sRebuiltURL);

</cfscript>
</cffunction>

</cfcomponent>
29 changes: 29 additions & 0 deletions examples/coldspring_aop/com/aspects/FormattingAroundAdvice.cfc
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<cfcomponent displayname="ReturnFormatter" extends="coldspring.aop.MethodInterceptor" hint="" output="false">

<cffunction name="setTaffyResourceObject" access="public" returntype="any">
<cfargument name="taffyResourceObject" type="taffy.core.resource" required="true" />
<cfset this.taffyResourceObject = arguments.taffyResourceObject />
</cffunction>

<cffunction name="invokeMethod" access="public" returntype="any">
<cfargument name="methodInvocation" type="coldspring.aop.MethodInvocation" required="false" />
<cfscript>

// Run the called method
local.methodReturnData = arguments.methodInvocation.proceed();

// Put the return data into a parent structre.
if (IsDefined("local.methodReturnData")) {

local.stReturnDataStructure = StructNew();
local.stReturnDataStructure["request_timestamp"] = Now();
local.stReturnDataStructure["data"] = local.methodReturnData;

return this.taffyResourceObject.representationOf(local.stReturnDataStructure).withStatus(200);

}

</cfscript>
</cffunction>

</cfcomponent>
Loading