Gregory's Blog

Using ColdFusion to Synchronize Time Across Time Zones


ColdFusion has plenty of date/time functions, however, it is lacking a robust set of native functions to calculate time stamps across time zones. Thankfully ColdFusion has a vibrant open-source community. In this article, I will walk you through the process of converting the client timestamp to the timestamp on the server for a given timezone using TimeZone.cfc.

Real-World Scenario

Galaxie Blog allows the author to select a post date in the past or in the future. When a future date is selected, Galaxie Blog will create a scheduled task to send the post to the subscriber base via email. However, we must convert the post date in the author's time zone to the proper time in the time zone of the server. We need to make sure that the emails are sent out at the right time if the time zones are different between the author and the server. To accomplish this we will use an open-source library, TimeZone.cfc.

Real World Example

Click the button below to see this example in action.

TimeZone.cfc

TimeZone.cfc is a small ColdFusion component that has many different methods to convert timestamp information between various timezones. This component allows for robust time zone conversion using Java's  java.util.TimeZone class.

It can be found on GitHub at https://github.com/rip747/TimeZone-CFC. However, the current version of TimeZone.cfc has a small bug caused by a nested comment and a small bug with CF2021.

I documented these bugs and made proposed changes on the original TimeZone repo and have forked my own TimeZone.cfc at https://github.com/GregoryAlexander77/TimeZone-CFC. Once you have downloaded the cfc you will need to upload it to the server. In this example, I placed the cfc in /blog/common/cfc/TimeZone.cfc.

Server-side function to convert a selected date and timezone between the client and the server

I created the following function to convert the timestamp on the client's device to the time found on the server. This is the same function that I am using in the demo below.

The required arguments are the date and time selected by the user, along with the author's time zone.

The TimeZone's getServerId() retrieves the time zone string found on the server. My server, hosted by Hostek, has the following identifier  'America/Chicago'.

Code on the client-side is sending the Time Zone's GMT Offset that the user selected. GMT is Greenwich Mean Time and is the time displayed by the Shepherd Gate Clock at the Royal Observatory in Greenwich, London. The GMT Offset is a numeric value that is used to determine the time from the GMT and is a string like so '-8'. If it is noon in London, you would subtract 8 hours to find the time in the Pacific Time Zone, which would be 4 AM, for example.

TimeZone cfc takes this offset value and extracts a list of time zone identifiers. The time zone identifier for my location is 'America/Los_Angeles' but you can select your own.

As there are many time zone identifiers for a given GMT Offset (-8 for example), and the blogTimeZoneList[1] code picks the first value found in this list. It is better to use the Time Zone Identifiers instead of calculating it using GMT Offset, but here we are using the GMT Offset for this example.

Near the bottom of the function, the convertTz function takes the date and timezone selected by the user and converts it to the date on the server. See the documentation in the TimeZone.cfc for more information.

<cffunction name="getServerDateTime" access="remote" returntype="date" returnformat="json" output="true"
		hint="Takes a date from the client and returns the date that it should be on the Server. This is used to schedule tasks on the server from the front end.">
	<cfargument name="selectedDate" type="date" required="true" />
	<cfargument name="selectedTime" type="date" required="true" />
	<cfargument name="timeZone" type="string" required="true" />

	<cfset thisDate = selectedDate & ' ' & selectedTime>

	<!--- Invoke the Time Zone cfc --->
	<cfobject component=".blog.common.cfc.TimeZone" name="TimeZoneObj">

	<!--- Get the time zone identifier on the server (ie America/Los_Angeles) from the TimeZone component --->
	<cfset serverTimeZoneId = TimeZoneObj.getServerId()>
	<!--- Get the blog time zone offset (-8) from the database and is populated by the Blog Time interface. We probably should be storing the actual identifier (America/Los_Angeles) in the database in the future to get the proper DST --->
	<cfset blogTimeZone = arguments.timeZone>
	<!--- Get the time zone identifier (America/Los_Angeles) by the GMT offset. This will pull up multiple options, but we just need a working identifier and will select the first one.  --->
	<cfset blogTimeZoneList = TimeZoneObj.getTZByOffset(blogTimeZone)>
	<!--- Get the first value in the array. We don't need this to be accurate, we just need a valid identifier to use. --->
	<cfset blogTimeZoneId = blogTimeZoneList[1]>

	<!--- Now use the convertTZ function to convert the blog time to server time. The blog time is the time zone of the site administrator that is writing the articles. We may want to add time zones for all blog users with the edit post role in the future. 
	convertTz(thisDate,	fromTZ, toTZ) --->
	<cfset serverDateTime = TimeZoneObj.convertTZ(thisDate, blogTimeZoneId, serverTimeZoneId)>

	<!--- Return it. --->
	<cfreturn serverDateTime>

</cffunction>

Client-Side Code

The server-side code found above should be a useful introduction to this library for most users, but for clarity, I will also include the client-side code. The client-side code uses the Kendo Core library, but you can use normal dropdowns and date pickers on the client-side if you choose. 

Client-Side Javascript

The following Javascript creates the necessary data and functionality of the dropdowns on the client.

The Kendo Time Picker widget prompts the user to select a valid date/time.

The Time Zone dropdown displays the label in the tzInts Javascript array, for example '(GMT-12:00) International Date Line West' and extracts the GMT Offset found in the selected value.

The getServerDateTime function sends these values using Ajax to the getServerDateTime function (found above) on the server.

The serverDateTimeResult function returns the getServerDateTime value back to the client and populates the serverTime HTML form.

<script>

var todaysDate = new Date();

// Kendo Dropdowns
// Date/time picker			
$("#selectedDate").kendoDateTimePicker({
	componentType: "modern",
	value: todaysDate
});

var tzInts = [
	{"label":"(GMT-12:00) International Date Line West","value":"-12"},
	{"label":"(GMT-11:00) Midway Island, Samoa","value":"-11"},
	{"label":"(GMT-10:00) Hawaii","value":"-10"},
	{"label":"(GMT-09:00) Alaska","value":"-9"},
	{"label":"(GMT-08:00) Pacific Time (US & Canada)","value":"-8"},
	{"label":"(GMT-08:00) Tijuana, Baja California","value":"-8"},
	{"label":"(GMT-07:00) Arizona","value":"-7"},
	{"label":"(GMT-07:00) Chihuahua, La Paz, Mazatlan","value":"-7"},
	{"label":"(GMT-07:00) Mountain Time (US & Canada)","value":"-7"},
	{"label":"(GMT-06:00) Central America","value":"-6"},
	{"label":"(GMT-06:00) Central Time (US & Canada)","value":"-6"},
	{"label":"(GMT-05:00) Bogota, Lima, Quito, Rio Branco","value":"-5"},
	{"label":"(GMT-05:00) Eastern Time (US & Canada)","value":"-5"},
	{"label":"(GMT-05:00) Indiana (East)","value":"-5"},
	{"label":"(GMT-04:00) Atlantic Time (Canada)","value":"-4"},
	{"label":"(GMT-04:00) Caracas, La Paz","value":"-4"},
	{"label":"(GMT-04:00) Manaus","value":"-4"},
	{"label":"(GMT-04:00) Santiago","value":"-4"},
	{"label":"(GMT-03:30) Newfoundland","value":"-3.5"},
	{"label":"(GMT-03:00) Brasilia","value":"-3"},
	{"label":"(GMT-03:00) Buenos Aires, Georgetown","value":"-3"},
	{"label":"(GMT-03:00) Greenland","value":"-3"},
	{"label":"(GMT-03:00) Montevideo","value":"-3"},
	{"label":"(GMT-02:00) Mid-Atlantic","value":"-2"},
	{"label":"(GMT-01:00) Cape Verde Is.","value":"-1"},
	{"label":"(GMT-01:00) Azores","value":"-1"},
	{"label":"(GMT+00:00) Casablanca, Monrovia, Reykjavik","value":"0"},
	{"label":"(GMT+00:00) Greenwich Mean Time : Dublin, Edinburgh, Lisbon, London","value":"0"},
	{"label":"(GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna","value":"1"},
	{"label":"(GMT+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague","value":"1"},
	{"label":"(GMT+01:00) Brussels, Copenhagen, Madrid, Paris","value":"1"},
	{"label":"(GMT+01:00) Sarajevo, Skopje, Warsaw, Zagreb","value":"1"},
	{"label":"(GMT+01:00) West Central Africa","value":"1"},
	{"label":"(GMT+02:00) Amman","value":"2"},
	{"label":"(GMT+02:00) Athens, Bucharest, Istanbul","value":"2"},
	{"label":"(GMT+02:00) Beirut","value":"2"},
	{"label":"(GMT+02:00) Cairo","value":"2"},
	{"label":"(GMT+02:00) Harare, Pretoria","value":"2"},
	{"label":"(GMT+02:00) Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius","value":"2"},
	{"label":"(GMT+02:00) Jerusalem","value":"2"},
	{"label":"(GMT+02:00) Minsk","value":"2"},
	{"label":"(GMT+02:00) Windhoek","value":"2"},
	{"label":"(GMT+03:00) Kuwait, Riyadh, Baghdad","value":"3"},
	{"label":"(GMT+03:00) Moscow, St. Petersburg, Volgograd","value":"3"},
	{"label":"(GMT+03:00) Nairobi","value":"3"},
	{"label":"(GMT+03:00) Tbilisi","value":"3"},
	{"label":"(GMT+03:30) Tehran","value":"3.5"},
	{"label":"(GMT+04:00) Abu Dhabi, Muscat","value":"4"},
	{"label":"(GMT+04:00) Baku","value":"4"},
	{"label":"(GMT+04:00) Yerevan","value":"4"},
	{"label":"(GMT+04:30) Kabul","value":"4.5"},
	{"label":"(GMT+05:00) Yekaterinburg","value":"5"},
	{"label":"(GMT+05:00) Islamabad, Karachi, Tashkent","value":"5"},
	{"label":"(GMT+05:30) Sri Jayawardenapura","value":"5.5"},
	{"label":"(GMT+05:30) Chennai, Kolkata, Mumbai, New Delhi","value":"5.5"},
	{"label":"(GMT+05:45) Kathmandu","value":"5.75"},
	{"label":"(GMT+06:00) Almaty, Novosibirsk","value":"6"},{"label":"(GMT+06:00) Astana, Dhaka","value":"6"},
	{"label":"(GMT+06:30) Yangon (Rangoon)","value":"6.5"},
	{"label":"(GMT+07:00) Bangkok, Hanoi, Jakarta","value":"7"},
	{"label":"(GMT+07:00) Krasnoyarsk","value":"7"},
	{"label":"(GMT+08:00) Beijing, Chongqing, Hong Kong, Urumqi","value":"8"},
	{"label":"(GMT+08:00) Kuala Lumpur, Singapore","value":"8"},
	{"label":"(GMT+08:00) Irkutsk, Ulaan Bataar","value":"8"},
	{"label":"(GMT+08:00) Perth","value":"8"},
	{"label":"(GMT+08:00) Taipei","value":"8"},
	{"label":"(GMT+09:00) Osaka, Sapporo, Tokyo","value":"9"},
	{"label":"(GMT+09:00) Seoul","value":"9"},
	{"label":"(GMT+09:00) Yakutsk","value":"9"},
	{"label":"(GMT+09:30) Adelaide","value":"9.5"},
	{"label":"(GMT+09:30) Darwin","value":"9.5"},
	{"label":"(GMT+10:00) Brisbane","value":"10"},
	{"label":"(GMT+10:00) Canberra, Melbourne, Sydney","value":"10"},
	{"label":"(GMT+10:00) Hobart","value":"10"},
	{"label":"(GMT+10:00) Guam, Port Moresby","value":"10"},
	{"label":"(GMT+10:00) Vladivostok","value":"10"},
	{"label":"(GMT+11:00) Magadan, Solomon Is., New Caledonia","value":"11"},
	{"label":"(GMT+12:00) Auckland, Wellington","value":"12"},
	{"label":"(GMT+12:00) Fiji, Kamchatka, Marshall Is.","value":"12"},
	{"label":"(GMT+13:00) Nuku'alofa","value":"13"}
]	

// timezone dropdown
var timeZoneDropdown = $("#timeZoneDropdown").kendoDropDownList({
	optionLabel: "Select...",
	dataTextField: "label",
	dataValueField: "value",
	filter: "contains",
	dataSource: tzInts,
	change: onTimeZoneChange,
}).data("kendoDropDownList");

// Save the selected date in a hidden field.
function saveTimeZoneValue(timeZoneId){
	$("#timeZoneValue").val(timeZoneId);
}

// Calculate the server time
function onTimeZoneChange(e){
	// Get the value
	getServerDateTime();
}//...function onBlogTimeZoneChange(e)

function getServerDateTime(){

	jQuery.ajax({
		type: 'post', 
		url: '<cfoutput>#application.baseUrl#</cfoutput>/demo/Demo.cfc?method=getServerDateTime',
		data: { // arguments
			selectedDate: kendo.toString($("#selectedDate").data("kendoDateTimePicker").value(), 'MM/dd/yyyy'),
			selectedTime: kendo.toString($("#selectedDate").data("kendoDateTimePicker").value(), 'hh:mm tt'),
			timeZone: $("#timeZoneValue").val()
		},
		dataType: "json",
		success: serverDateTimeResult, // calls the result function.
		error: function(ErrorMsg) {
			console.log('Error' + ErrorMsg);
		}
	// Extract any errors. This is a new jQuery promise based function as of jQuery 1.8.
	}).fail(function (jqXHR, textStatus, error) {
		// The full response is: jqXHR.responseText, but we just want to extract the error.
		$.when(kendo.ui.ExtAlertDialog.show({ title: "Error while consuming the getServerDateTime function", message: error, icon: "k-ext-error", width: "<cfoutput>#application.kendoExtendedUiWindowWidth#</cfoutput>" }) // or k-ext-error, k-ext-information, k-ext-question, k-ext-warning.  You can also specify height.
			).done(function () {
			// Do nothing
		});
	});//...jQuery.ajax({
};

function serverDateTimeResult(response){
	// alert(response);
	// The server is returning the time. Save this into the form for display
	$("#serverTime").val(response);
}

Client-Side HTML

<div class="content k-content">
<table align="center" class="k-content" width="100%" cellpadding="2" cellspacing="0">
  <input type="hidden" name="selectedDateValue" id="selectedDateValue" value="">
  <input type="hidden" name="timeZoneValue" id="timeZoneValue" value="">
  <input type="hidden" name="serverTimeZoneValue" id="serverTimeZoneValue" value="">
  <cfsilent>
	<!---The first content class in the table should be empty. --->
	<cfset thisContentClass = HtmlUtilsObj.getKendoClass('')>
	<!--- Set the colspan property for borders --->
	<cfset thisColSpan = "2">
  </cfsilent>

  <tr height="1px">
	  <td align="left" valign="top" colspan="2" class="<cfoutput>#thisContentClass#</cfoutput>"></td>
  </tr>
  <tr height="1px">
	  <td></td>
	  <td align="left" valign="top" class="<cfoutput>#thisContentClass#</cfoutput>">
		Your hosting provider or server may reside in a different time-zone. These settings are critical when this is the case. If your server is in a different time-zone, you will want the post date to show the  time that you are in- not necessarilly where the server is.
	  </td>
  </tr>
  <!-- Form content -->
<cfif session.isMobile>
<tr valign="middle">
<td class="<cfoutput>#thisContentClass#</cfoutput>" colspan="2">
	<label for="selectedDate">Date</label>
</td>
</tr>
<tr>
<td class="<cfoutput>#thisContentClass#</cfoutput>" colspan="2">
	<input id="selectedDate" name="selectedDate" value="<cfoutput>#dateTimeFormat(selectedDate, 'medium')#</cfoutput>" style="width: <cfif session.isMobile>95<cfelse>45</cfif>%" /> 
</td>
</tr>
<cfelse><!---<cfif session.isMobile>--->
<tr valign="middle" height="30px">
<td align="right" valign="middle" class="<cfoutput>#thisContentClass#</cfoutput>" width="20%">
	<label for="selectedDate">Date</label>
</td>
<td align="left" class="<cfoutput>#thisContentClass#</cfoutput>">
	<input id="selectedDate" name="selectedDate" value="<cfoutput>#dateTimeFormat(now(), 'medium')#</cfoutput>" style="width: <cfif session.isMobile>95<cfelse>45</cfif>%" /> 
</td>
</tr>
</cfif>
  <!-- Border -->
  <tr height="2px"> 
	  <td align="left" valign="top" colspan="<cfoutput>#thisColSpan#</cfoutput>" class="<cfoutput>#thisContentClass#</cfoutput>"></td>
  </tr>
<cfif session.isMobile>
  <tr valign="middle">
	<td class="<cfoutput>#thisContentClass#</cfoutput>" colspan="2">
		<label for="timeZoneDropdown">Your time-zone:</label>
	</td>
   </tr>
   <tr>
	<td class="<cfoutput>#thisContentClass#</cfoutput>" colspan="2">
		<select id="timeZoneDropdown" name="timeZoneDropdown" style="width:95%" onchange="saveTimeZoneValue(this.value)"></select>
	</td>
  </tr>
<cfelse><!---<cfif session.isMobile>---> 
  <tr>
	<td align="right" class="<cfoutput>#thisContentClass#</cfoutput>" style="width: 20%"> 
		<label for="timeZoneDropdown">Your time-zone:</label>
	</td>
	<td class="<cfoutput>#thisContentClass#</cfoutput>">
		<select id="timeZoneDropdown" name="timeZoneDropdown" style="width:50%" onchange="saveTimeZoneValue(this.value)"></select>
	</td>
  </tr>
</cfif>	  
  <!-- Border -->
  <tr height="2px">
	  <td align="left" valign="top" colspan="<cfoutput>#thisColSpan#</cfoutput>" class="<cfoutput>#thisContentClass#</cfoutput>"></td>
  </tr>
  <cfsilent>
  <!--- Set the class for alternating rows. --->
  <!---After the first row, the content class should be the current class. --->
  <cfset thisContentClass = HtmlUtilsObj.getKendoClass(thisContentClass)>
  </cfsilent>
<cfif session.isMobile>
  <tr valign="middle">
	<td class="<cfoutput>#thisContentClass#</cfoutput>" colspan="2">
		<label for="serverTime">Hostek Server Time:</label>
	</td>
   </tr>
   <tr>
	<td class="<cfoutput>#thisContentClass#</cfoutput>" colspan="2">
		<input type="text" id="serverTime" name="serverTime" value="" class="k-textbox" required> (Oklahoma)
	</td>
  </tr>
<cfelse><!---<cfif session.isMobile>---> 
  <tr>
	<td align="right" class="<cfoutput>#thisContentClass#</cfoutput>" style="width: 20%"> 
		<label for="serverTime">Hostek Server Time:</label>
	</td>
	<td class="<cfoutput>#thisContentClass#</cfoutput>">
		<input type="text" id="serverTime" name="serverTime" value="" class="k-textbox" required> (Oklahoma)
	</td>
  </tr>
</cfif>
</table>

This entry was posted on June 21, 2022 at 2:40 AM and has received 156 views.

Using Cookies to Pass JavaScript Variables to ColdFusion


Understanding the differences between Javascript and ColdFusion

You can't directly pass a Javascript variable to ColdFusion as the two technologies are quite different.

ColdFusion is processed on the server side prior to delivering the content to the client. ColdFusion prepares the entire page and then renders it to the client before any client-side activity, such as Javascript, can take place.

Javascript is used on the client-side, and these variables are only available after ColdFusion initially delivers the HTML to the client. These two environments can not be running at the same time. 

To pass Javascript variables to ColdFusion, you must an assistive technology, such as Ajax, or pass the Javascript variables to the server-side once the initial page has been rendered. Here we will focus on using Cookies to transfer information from the client-side to the server-side.

Real-world scenario

In Galaxie Blog, I developed a complex create post interface with TinyMce. There are scores of different interfaces depending upon the client's screen resolution and device. I could have used media queries and other client-side techniques to build the page, however, the enormous complexity of the various interfaces made it much easier to design these interfaces on the server-side. 

To accomplish this, I sniffed the client's device by intercepting the HTTP user agent string with ColdFusion scripts provided by detectmobilebrowers.com. This works flawlessly and detects mobile and desktop devices but does not work with newer iPads. Unfortunately, Apple recently removed any identifying strings and made it impossible to detect iPads using the HTTP user agent string.To determine iPad clients, I would have to use Javascript to obtain the screen resolution and pass it to ColdFusion.

Using cookies to pass Javascript variables to ColdFusion

You can use cookies to transfer information from Javascript to ColdFusion. This particular technique requires that the user land on a page, such as a login form, to obtain the needed information from Javascript. 

In my scenario, the user will first hit a login page. The login page has the following Javascripts to set a cookie that ColdFusion can read. Note the path argument ('/') at the end of the script. You must store the cookie that Javascript creates in the root directory for ColdFusion to be able to read the cookie.

Logic on the client-side

/* Cookie functions. The original author of this script is unknown */
function setCookie(name,value,days) {
	var expires = "";
	if (days) {
		var date = new Date();
		date.setTime(date.getTime() + (days*24*60*60*1000));
		expires = "; expires=" + date.toUTCString();
	}
	// The path must be stored in the root in order for ColdFusion to read these cookies
	document.cookie = name + "=" + (value || "")  + expires + "; path=/";
}

Once the user hits the login page, set a cookie using the Javascript function above. We are naming our cookies 'screenWidth' and 'screenHeight' and passing the width and the height of the device determined by Javascript and keeping the cookie alive for one day.

// Set a cookie indicating the screen size. We are going to use this to determine what interfaces to use when the the screens are narrow.
// (setCookie(name,value,days))
setCookie('screenWidth',$(window).width(),1);
setCookie('screenHeight',$(window).height(),1);

Server-side logic using ColdFusion

To read the cookie using ColdFusion, it is best to use a try block to read the cookie. The syntax to read the cookie from Javascript is different than the native ColdFusion cookie methods- note the brackets surrounding the cookie name. 

<!--- Get client properties. This will be used to set the interfaces depending upon the screen size --->
<cftry>
	<cfset screenHeight = cookie['screenHeight']>
	<cfset screenWidth = cookie['screenWidth']>
	<cfcatch type="any">
		<cfset screenHeight = 9999>
		<cfset screenWidth = 9999>	   
	</cfcatch>
</cftry>
<!--- Outputting the screen sizes --->
<cfoutput>
screenHeight: #screenHeight#
screenWidth: #screenWidth#
</cfoutput>

You can use this particular method to transfer any variable obtained by Javascript to ColdFusion as long as you have the user first hit a landing page. 

I may write other articles using other methods, such as Ajax, in the future. 

 

This entry was posted on June 16, 2022 at 1:08 AM and has received 211 views.

Enabling Search Engine Friendly Links with Url Re-write in Galaxie Blog


To enable search engine-friendly URL's in Galaxie blog, follow the steps below.

  1. If you're using IIS, the following rule should be copied and pasted into the web.config in the root directory on your web server. The URL redirection rule is between the rewrite tags below. It essentially matches all strings with 'index.cfm', and removes this string. Additionally, it sets a permanent redirect that the search engine uses when crawling your page.  If you're using a different web server, see the links at the bottom of this article or consult with your server administrator. 
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <directoryBrowse enabled="false" />
        <urlCompression doStaticCompression="true" doDynamicCompression="true" />
		<rewrite>
		<rules>
		    <rule name="GregorysBlog" stopProcessing="true">
                        <match url="(.*)index.cfm" />
                            <conditions logicalGrouping="MatchAll">
                                <add input="{SCRIPT_FILENAME}" matchType="IsFile" negate="true" />
                                <add input="{QUERY_STRING}" pattern=".+" ignoreCase="false" negate="true" />
                        </conditions>
                        <action type="Redirect" url="{R:1}" appendQueryString="true" redirectType="Permanent" />
                  </rule>
	    </rules>
	</rewrite>
        <security>
            <requestFiltering>
                <fileExtensions>
                    <add fileExtension=".pl" allowed="false" />
                </fileExtensions>
            </requestFiltering>
        </security>
        <httpErrors errorMode="Detailed" />
        <staticContent>
            <remove fileExtension=".woff2" />
            <mimeMap fileExtension=".woff2" mimeType="font/woff2" />
            <remove fileExtension=".webp" />
            <mimeMap fileExtension=".webp" mimeType="image/webp" />
        </staticContent>
    </system.webServer>
</configuration>
  1. Log into the Galaxie Blog Administrator and click on the Blog options icon. 
  2. Check the Server Rewrite Rule in place checkbox.
  3. Click on the submit button at the bottom of the page.

That's it! Your blog should now be using search-friendly URLs.

Credits:

  • I especially want to thank Caleb C. from Hostek for helping me get the IIS rule straight. The folks at Hostek have been nothing but outstanding in their service and support!

Further Reading:

This entry was posted on April 26, 2022 at 7:24 PM and has received 1174 views.

Extending Application.cfc's using mappings and proxies


This is a rather long article, if you want to jump to the condensed summary, scroll down to the bottom of this page. Many years ago, the first time that I tried to perform this, I received the following message no matter what I tried: "Could not find the ColdFusion component or interface xxx'. In a nutshell, the problem using this approach is that both the root and the subfolders have the same name, i.e. Application.cfc, and ColdFusion can't properly identify what component to extend. Finally, after some serious investigation, someone came up with the idea to create a proxy.cfc that resides in the same root directory as the root Application.cfc, and the Application.cfc in the subfolder extends an empty proxy.cfc that extends the root cfc like so:
root directory: Application.cfc
This root Application.cfc does not extend anything

Also in the root directory: Proxy.cfc
Proxy.cfc has the following code, its essentially empty. The only thing that the Proxy.cfc does is to extend the Application.cfc that is in the same directory:
<cfcomponent extends="Application">

</cfcomponent>


Subdirectory such as a folder named admin.
This subdirectory has another Application.cfc. Let's say that this component is responsible for securing the application and has login logic as well as debugging settings for example. This Application.cfc will extend the Proxy.cfc to gain the methods and properties of the Application.cfc in the root directory like so:
<cfcomponent displayname="Admin" extends="Proxy.cfc">
<!-- Lots of code --->

</cfcomponent>

This approach was a godsend and it was heavily blogged about. Ben Nadel has made a number of very helpful posts which I will share at the bottom of this article. This works quite well unless you're on a hosted domain or a server that uses virtual directories. In this case, we are in the same original boat in which we started from. Now we are back into the "Could not find the ColdFusion component or interface xxx' hell! There is a solution for this tricky problem though, we need to also use mapping! It is a common misnomer that you can't use mapping to extend components. I am not quite sure where this misconception originally came about, but it has been proven that this is just not true. There are occasions where we must use mapping to solve some annoying problems, like here. This particular site is hosted by hostek.com. They are a fine company to deal with, but the server that my site is hosted on has some idiosyncrasies due to the directory structure. Here, when I use the Proxy.cfc method to extend the logic from the base Application.cfc to the Application.cfc in the admin folder I receive the dreaded 'could not find the ... component' error. When I first saw it I was dismayed thinking not this again, so I turned to ColdFusion CFC mapping. Mapping tells ColdFusion where to find the file and what the file relationships are.
Let's review CFC structure that was just discussed. For example, imagine the following directory structure:
root directory: i.e. www.gregoryalexander.com/
subdirectory: www.gregoryalexander.com/admin/
As discussed, we have an Application.cfc and the Proxy.cfc in the root directory, and we have the Application.cfc in the 'admin' subdirectory. The Proxy.cfc extends the Application.cfc, also in the root directory, and the Application.cfc in the subdirectory (admin) extends the Proxy.cfc in the root directory. root directory: contains both Application.cfc and Proxy.cfc (that extends the root Application.cfc).
subdirectory: Application.cfc (that extends Proxy.cfc).
Now we need to also add the following mapping in the root Application.cfc. This mapping logic should be near the top of the root Application.cfc, and it should not be within any of the Application.cfc event handlers (onApplicationStart, onApplicationRequest, etc). This mapping code does not need to be anywhere else other than the root Application.cfc:
<!-- Define application-specific mappings. These will be used to point to this application.cfc when we extend it in the admin/Administrator.cfc template using the Proxy.cfc that resides in the same folder as this Application.cfc. --->

<cfset this.mappings="structNew()" />
<!-- Mapping for the ROOT Application.cfc --->

<cfset this.mappings["rootCfc"]="getDirectoryFromPath(getCurrentTemplatePath())" />
<!-- Mapping for the admin SUBDIRECTORY Application.cfc. Note the admin prefix is attached at the end of this line. This points to the admin folder. --->

<cfset this.mappings["adminCfc"]="getDirectoryFromPath(" getCurrentTemplatePath()="" &="" "="" admin"="" )="" />
I used rootCfc to identify the Application.cfc in the root directory, whereas adminCfc applies to the Application in the admin directory. These variables can be named anything. Note that the "/admin" string at the end of the adminCfc mapping points to the 'admin' folder, which is a subdirectory. Now that we have the mappings in the root Application.cfc, we need to apply them to the extends statement in Application.cfc located in the subdirectory. In the /admin/Application.cfc template use:
/admin/Application.cfc
<cfcomponent displayname="xxx" sessionmanagement="xx" clientmanagement="xx" extends="rootCfc.Proxy">
<!-- Logic --->

</cfcomponent>
Of course, rootCfc tells the Application.cfc in the subdirectory to look for the Proxy.cfc template in the root directory. Like other 'extend' statements, you don't need to specify '.cfc' at the end of Proxy. You don't need to use this 'extend' mapping in either the root Proxy.cfc or Application.cfc templates. They can already find each other as they are both in the same root directory. /Proxy.cfc
<cfcomponent extends="Application">

</cfcomponent>

Summary

For the sake of absolute clarity:
root Application.cfc
Contains the mapping logic. Has the mappings for both of the root and subdirectory.
This mapping logic should be near the top of the root Application.cfc, and it should not be within any of the Application.cfc event handlers (onApplicationStart, onApplicationRequest, etc).
Does not use an 'extend' statement
<cfset this.mappings="structNew()" />

<cfset this.mappings["rootCfc"]="getDirectoryFromPath(getCurrentTemplatePath())" />

<cfset this.mappings["adminCfc"]="getDirectoryFromPath(" getCurrentTemplatePath()="" &="" "="" admin"="" )="" />
root Proxy.cfm
A simple 'extends="Application" works.
No mapping logic.
<cfcomponent extends="Application">

</cfcomponent>
subdirectory Application.cfc
The extends statement must be the mapping variable name of the folder (rootCfc), a dot (.), and finally the name of the Proxy.cfc template without the .cfc prefix (Proxy)
No mapping logic.
<cfcomponent displayname="Admin" sessionmanagement="yes" clientmanagement="yes" extends="rootCfc.Proxy">

</cfcomponent>
My apologies for being so verbose. I annoyed myself while writing this- but not as annoyed when I was while trying to solve this problem! Take care! Related External Posts:

This entry was posted on January 30, 2021 at 12:31 AM and has received 955 views.

Common Hibenate/ColdFusion ORM Errors


I have worked with ColdFusion ORM for several years now, and one of the most frustrating things working with CF ORM is the lack of friendly error messages. It should be noted that this is not necessarily Adobe's fault- Hibernate, the technology used by ColdFusion provides most of these error messages. 

I have compiled a list of common error messages and will try to explain why the error is occurring along with potential resolutions. You may find this useful whether developing Java or ColdFusion applications.

Table of Contents

500 error with no error message on the page, however, the following error shows up in the ColdFusion application log: coldfusion/orm/hibernate/ConfigurationManager.

This error may be caused as you have a reserved keyword in one of your persistent cfc's or in the cfclocation set in the application.cfc template.

Generally, this error will be raised before Hibernate is fully loaded and it is not due to any mapping issues. The first thing that I would suggest is to set the globally_quoted_identifiers argument to true: this.ormsettings.hibernate.globally_quoted_identifiers = true. This will escape any special reserved keywords that you may have.

If this does not work, you can also try turning off errors at the ORM level by turning cfc errors off with 'this.ormsettings.skipCFCWithError = true', however, I am not sure if this approach will work. I suspect that the error is raised even before loading any of the persistent cfc's that may cause an error.

In my case, I received this error after upgrading from CF2016 to CF2021 and the error was due to using the reserved keyword 'model' as the folder name that I used to store the persistent cfc's in. However, I did not get any error even with debugging turned on. I had to look into the ColdFusion logs and try to guess where the error came from. Changing the folder from mapping to 'galaxieDb' resolved my particular error thankfully.

A different object with the same identifier value was already associated with the session

This could be caused by several issues, namely:

  1. This could be due to a unique identifier being duplicated when you save the entity. Check your primary key auto generators to see if the primary key value is duplicated somewhere.
  2. A well-known Hibernate issue is when the memory is holding relationships between various objects. If you are using Java and Hibernate and your using cascade options, use 'merge' instead of using all.

For ColdFusion, you should eliminate cascade options with all pseudo columns that don't use a one-to-one relationship type. Cascade should not be used with many-to-one or many-to-many relationships according to Adobe

Attribute linkTable missing from the property

You need to add a link table to your existing many-to-many relationships. 

coldfusion.orm.PersistentTemplateProxy cannot be cast to java.util.Collection to an incompatible type

This may be due to incorrectly mapping a one-to-one relationship when the two entities actually have a many-to-x or x-to-many relationship. This error is reporting that it is expecting an array instead of a single string. Look at the entity's existing one-to-one relationships to find out if there is a missing x-to-many or many-to-x relationship.

Complex object types cannot be converted to simple values and Property true not present in the entity errors

You may be getting these errors when you are trying to make changes to an object that is returning multiple records.

Adobe provides documentation that you may set filters and arguments to load a single record, but I have not gotten this approach to work and had to rewrite my logic to ensure that a single record is always retrieved. 

Here is the relevant code to load a unique record from an array found on the Adobe site:

EntityLoad(entityName,[Filter="",unique="",options=""])

I have set the unique argument to true and have used an additional maxresults argument to attempt to load a single record, but this approach failed with the following error message- "Property true not present in the entity"

See below for a list of approaches that I have tried to unsuccessfully use.

<cfset CommenterRefDbObj = entityLoad("Commenter", { Email = arguments.email }, "true", {maxresults=1})> ('Property true not present in the entity.')
<cfset CommenterRefDbObj = entityLoad("Commenter", { Email = arguments.email }, true, {maxresults=1})> ('Property true not present in the entity.')
<cfset CommenterRefDbObj = entityLoad("Commenter", { Email = arguments.email }, unique="true", {maxresults=1})> ('Invalid CFML construct found on line x')
<cfset CommenterRefDbObj = entityLoad("Commenter", { Email = arguments.email }, {unique="true"}, {maxresults=1})> ('Complex object types cannot be converted to simple values.')
And others....

To address this problem, you will have to rewrite your logic to ensure that only a single record exists before loading the entity prior to using its set methods. I have not found a reliable way to pick out the record from an array when using ORM.

could not extract ResultSet

Hibernate error thrown when a many-to-many join table is not correct. Check your join tables for all relationships that have many-to-many relationships

Error casting an object of type to an incompatible type.

You will receive this error when you're using a link table and using the set methods of other entities when setting nonprimitive value types. This is due to the link table not having relationships mapped in the field type argument.

When you try to create a relationship in a link table that you are trying to create manually, you will receive a 'many-to-many requires a link table'. When you specify a link table in another entity that requires a many-to-many mapping, the link table is automatically assumed as having a many-to-many relationship and you need to leave the fieldtype relationship out. However, since the link table does not have a mapped relationship (ie fieldtype="many-to-many") you can't use objects to populate the columns since there is no mapped relationship. Instead, you must use primitive data types when setting the column value, such as integers.  

Error while executing the Hibernate query.

org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected AST node

I received this error when I was using a function to use HQL to query the database using a variety of filters such as WHERE Post LIKE 'x' AND Post.Remove = 0, etc. This is probably a bug with the ColdFusion implementation of HQL, however, you can generally fix this issue by limiting your filters to one or two WHERE clauses. If you are using Hibernate and not ColdFusion HQL, try setting the nativeQuery argument to true, i.e.: @Query(value = "SELECT Post.PostId FROM Post WHERE Post LIKE '%x'",
 nativeQuery = true)

Deleted object would be re-saved by cascade (remove deleted object from associations)

If you receive this error, you must remove all associations for this record belonging to other tables. For example, I have a many-to-one relationship between a blog category and a blog. Each blog may have multiple categories. However, when I try to delete the category without deleting the reference to the blog it throws this error.

To delete the category you must first set the category tables blog reference to null using the javacast method and then in a separate transaction delete the category like so:

<cftransaction>
    <!---  Delete the association to the blog table. --->
    <;!--- Load the comment entity. --->
    <cfset CategoryDbObj = entityLoad("Category", { CategoryId = arguments.categoryId }, "true" )>
    <!--- Remove the blog reference in order to delete this record --->
    <cfset CategoryDbObj.setBlogRef(javaCast("null",""))>
    <!--- Save it --->
    <cfset EntitySave(CategoryDbObj)>
</cftransaction>

<cftransaction>
    <!--- Now, in a different transaction, delete the record. --->
    <!--- Load the comment entity. --->
    <cfset CategoryDbObj = entityLoad("Category", { CategoryId = arguments.categoryId }, "true" )>
    <!--- Delete it --->
    <cfset EntityDelete(CategoryDbObj)>
</cftransaction>

Element x is undefined in a Java object of type class java.util.HashMap.

Check to see if you have a null in the database column. Replace the null with an empty string.

Error casting an object of type java.lang.Integer to an incompatible type

This could be due to a maddening error when you use 'WHERE 0=0 AND' in your SQL when you use a column that is a foreign key and you're using a cfqueryparam to pass the UserId. For example, the UserRef column in the IpAddress table is a foreign key to the Users.UserId column and the two entity keys have a relationship (many-to-one, one-to-one, etc). The following statement will cause this error:

SELECT new Map (
    IpAddress.IpAddress as IpAddress
)
FROM IpAddress as IpAddress 
WHERE 0=0
    AND UserRef = <cfqueryparam value="#getUserId#" cfsqltype="integer">

To resolve this, simply drop the WHERE 0=0 clause and use the following instead:

SELECT new Map (
    IpAddress.IpAddress as IpAddress,
)
FROM IpAddress as IpAddress 
WHERE UserRef = <cfqueryparam value="#getUserId#" cfsqltype="integer">

Error while resolving the relation between CFC common.cfc.db.model.ThemeSetting and CFC common.cfc.db.model.Theme because 'fkcolumn' is specified on both sides.

The fkcolumn must be specified on the side whose table has the fkcolumn. On the other side, you must specify mappedby.

expecting CLOSE, found 'ThemeSettingRef' near line x, column x

You may be missing a comma after or before a column, ie SELECT FirstName LastName FROM User. Note the missing comma between the first and last name columns.

expecting CLOSE, found 'x'

You may have a missing comma after a column in your HQL query.

illegal attempt to dereference collection [{synthetic-alias}

You may be trying to use a collection, or an array to join two classes without an alias name.

java.sql.SQLException: [Macromedia][SQLServer JDBC Driver][SQLServer]Ambiguous column name 'x'

Properly reference any database columns that may be in two or more tables.

org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected token: )

There is an extra comma at the end of the select statements after the last column. The ')' signifies that you are using a SELECT New Map (col1, col2,) for example.

org.hibernate.MappingException: TableX refers to an unmapped class

There may be some class name issue that is causing a hiccup with hibernate when you have multiple ORM databases.

Generally, the error does not provide which relationship is at fault, so you may have to comment out each relationship (one-to-many and many-to-one) to find out the relationship is causing the error. Don't get locked in and assume the broken relationship, there seems to be a missing class name in the XML that Hibernate generates behind the scenes.

I have only had this error twice during my ORM experience and I am not sure what the cause was. With ColdFusion, I don't know of any way to fix the problem when creating the initial database schema, but I have found that I can remove the relationship during the database creation and successfully add the offending relationship back into the table after the database has been created. 

ORM is not configured for the current application

  1. You may have duplicate ORM settings in different parts of your Application.cfc. Generally, you should have the ORM Settings, along with ORMReload(), in the OnApplicationStart in your application.cfc and remove OrmReload in the OnRequestStart (see http://nm1m.blogspot.com/2010/09/working-on-little-app-in-coldfusion-9.html).
  2. Make sure that ORMReload and InitOrm are not conflicting with each other. You can't have both statements in the same logical block.

If you get a "The passed value does not evaluate to a valid array object" error, it is due to the one-to-many relationship set to the type of an array in the entity declaration.

Path expected for join!

A SQL join was not set correctly.

Root cause :org.hibernate.HibernateException: Property : x - Object of type class java.lang.String cannot be used as an array

The following error is also related to having an incorrect one-to-one relationship when it expects an x-to-many or many-to-x relationship.

Session is closed' or 'Session Closed!

You can't process two or more different database sessions at the same time if you leave the default ORM settings as they are. If you are getting session errors with ORM, change the ORM settings to flushAtRequestEnd=false (<cfset this.ormsettings.flushAtRequestEnd = false>).

After setting this, you must change your code to use cftransactions around all of your ORM operations in order to flush the results unless you choose to manually use flush to maintain your ORM sessions programmatically. 

Another issue may be that there are errors in your CRUD operations code, especially if your crud operations are within a loop. What may be going on is that the error is trying to be reported, however, the ORM session that caused the code can't report the error internally as a new ORM session is being created and the original session is closed before it can report an error.

When this error happens you may also get a cryptic 'Unknown service requested [org.hibernate.stat.spi.StatisticsImplementor]; nested exception is org.hibernate.service.UnknownServiceException: Unknown service requested [org.hibernate.stat.spi.StatisticsImplementor]' error.

To fix this error, check to see if your code works with one record first. If it works, then try to code within the looping structure again.

The system has attempted to use an undefined value, which usually indicates a programming error, either in your code or some system code.

There are two issues that I am documenting here:

You may be using a cfquery param tag in HQL on a primary key or relationship that has a relationship. Remove the cfquery param.

Another issue is that you may have a table name and a column name that is identical. For example, when I received this error I had a Role table and a Role column. Hibernate was confused between the object and the column when the column names are identical.

The fix here is to name the column something different than the table- such as Role for the table name, and RoleName for the column name. This issue caught me several times as I typically name the column holding the entity value the same as the table name (ie table: Role, column: Role). This is not a good idea when using ORM.

Note: Null Pointers are another name for undefined values.

This could also be the result of a ColdFusion or Hibernate bug. When you try to use an HQL query that specifies another join to a different nested object and you don't specify which item to pull from the nested array, any column listed after that will raise this error message. See the Theme and Theme Setting HQL query for more information.

You have attempted to dereference a scalar variable of type class java.util.ArrayList as a structure with members.

You may have forgotten to add the index of the array like so:

<cfset BlogDbObj = entityLoadByPk("Comment", 1)>

This entry was posted on June 8, 2022 at 7:56 PM and has received 327 views.




Footer Logo

Your input and contributions are welcomed!

If you have an idea, BlogCfc based code, or a theme that you have built using this site that you want to share, please contribute by making a post here or share it by contacting us! This community can only thrive if we continue to work together.

Images and Photography:

Gregory Alexander either owns the copyright, or has the rights to use, all images and photographs on the site. If an image is not part of the "Galaxie Blog" open sourced distribution package, and instead is part of a personal blog post or a comment, please contact us and the author of the post or comment to obtain permission if you would like to use a personal image or photograph found on this site.

Credits:

Portions of Galaxie Blog are powered on the server side by BlogCfc, an open source blog developed by Raymond Camden. Revitalizing BlogCfc was a part of my orginal inspiration that prompted me to design this site.

Version:

Galaxie Blog Version 3.0 (Toby's Edition) June 14th 2022 Bahama Bank theme