Zombie Undead?

Note: This is a personal perspective, not the official response from OpenText Corporation.

Am I part of the Zombie Apocalypse?

Tony is right he blogs about this product (OpenText Web Site Management, formerly RedDot CMS & LiveServer) at least once every few years (no news). He self-professed fell out of love in 2007 and has gone negative ever since. If this is the first article you’ve seen, from my perspective, RSG steadily puts out blog posts to stir up doubts in peoples current platforms or ones they may be contemplating for selection. These target not just OpenText, although our leadership in Enterprise Information Management (ECM, WCM, DAM, Portal, and more topics they cover) makes us a big target as a lead generation mechanism. All this comes with the not so subtle hint to select RSG for consulting to help pick your next vendor. I’ll steer clear of calling them sensational, but there are times I’ll read the articles myself for bulletin-board material whether on us or a competitor. I don’t mind he sees us as “undead”.  Current and new customers realize to “… put technology of all types, especially content management, into its proper context — It’s neither the reason for success nor the reason for failure. “(Scott Liewehr, 2012). It seems Tony hasn’t noticed OpenText has formalized new channels for the Web Site Management product. Perhaps this calls into question the Portal reviews in comparison to his competitors who have us as part of the solution in the upper right corner (I’m not talking about OT Portal, but we do that too). Interesting when one analyst calls something out no multi-channel and another gives specific examples of web content with multi-channel reuse capabilities. There blog is free and they are selling something just like I am, I wish them the best of luck and I hope they are impartial in their selection process if not their editorial tone.

For me I look at the Vignette acquisition forced a pivot in 2009. OpenText as all the selection analysts say agrees that no single WCM fits every customer need. Many analysts say you may need multiple WCM systems for you enterprise. If that is true why wouldn’t vendors have and be able to maintain a series of solutions that fit more of the customers needs, OpenText have since 2006 on ECM (Document Management). In 2009-2010 OpenText discussed this as a continuum of WCM Maturity, similar to AIIM Maturity model for ECM. This is now really part of a spectrum of simple WCM to WEM. Both OpenText solutions have a place in Customer Experience Management for different organizations, sometimes in the same organization. Sure there are some overlaps but OpenText partners with organization to define when and where to use each or our tools. In 2012 I read The Innovators Dilemma and Understanding Michael Porter. This gave some perspective. What you see from many former mid-market solutions (SDL Tridion, Adobe CQ5, OpenText WSM, SiteCore) is they move up market as new lower cost followers enter the market. Sometimes they succeed or fail, often if they only focus on that up-market move not a core value proposition and the audience interested they won’t evolve and will later see disruption. Often there is quite a few challenges their customers face as they move up-market because resources are thin, partners are hoping on the bandwagon of the hot new WCM in town. With the Vignette acquisition OpenText stopped the WSM push to increasingly up-market and has been refocusing on the core value propositions and value chains. Why did our current customers acquire our software? We are an editor focused, multi-language, multi-variant (read multi-Channel for non-project builders), markup/development language agnostic, not requiring specific developer skills, solution with baking at its core that drew people to the solution in the first place.  In the last two years R&D has focused on paying down our well documented technical debt (today’s Management Server release is a fully retooled 64-bit .Net 4 back-end). We aren’t done. We did this without a forced architectural/language switch like Vignette from v6 to v7 or Documentum, as was originally planned.  This has allowed our customers to enjoy a refreshed SmartEdit UI, add-on of proven enterprise Social in OpenText Tempo Social, and a continued evolution. In North America we’ve refocused on our core, verticals, and Alliances (SAP & Microsoft).

For those of you who’ve picked RedDot or OpenText Web Site Management and long term active partners (OpenText, Enthink, or others) have for the most part seen great value in the solution. I have a view that IT/Software is a human process and always there is room for improvement. If you are a customer struggling I’d hope you’d reach out to renew your partnership with us or a partner we’d recommend to help you in a turn around. Sitting down with Arek and Wojtek (from Enthink) at Enterprise World in November we all saw a bright new era for our customers and prospects with what the OpenText’s EIM strategy can mean as OpenText are no longer scope locked on marketing/developing to support growth in ECM alone. This was a sentiment I got from the WSM customers I spoke with there as well. WCM (or pick your favorite acronym) isn’t the only part of your sites (internal, public or private), and never was.

Now can I look forward to another year as a “zombie” having my team work with our customers and partners. I look forward to the launch and relaunches of some exciting web properties, you want to know more just ask.  My team will soon publicly launch a new solution to support redesigns, micro-sites and illustrate you our standard best practices at a price-point all can budget for. With that I guess I’ll close by saying Happy New Year & enjoy the brains!

Adventures in Form Handling – II

The next step was generating the query with DS Attributes. Nice thing about SQL is it doesn’t care about line breaks, so you can condense it down to one line. This is good as the XML engine in Delivery Server may not preserve these. I didn’t test conclusively, if you need white-space or formatting for say a REST call you will want to test thoroughly.

This is how I broke down the SQL to ‘templatize’ it for Delivery Server. Each line that needs Delivery Server variables is broken out.

DECLARE @formID as varchar(50) DECLARE @InsertOutputForm table (  form_id varchar(50) );DECLARE @InsertOutputFormFields table( id varchar(50), form_id  varchar(50), name  varchar(50),  type varchar(50)); INSERT INTO dbo.forms (name,type,dsuser) OUTPUT    INSERTED.id as form_id  INTO @InsertOutputForm 
VALUES (N'partnerinfoform', N'form',N'cindy')
SET @formID=(SELECT form_id FROM @InsertOutputForm); INSERT INTO dbo.form_fields (form_id, name, type, data)OUTPUT INSERTED.id, INSERTED.form_id, INSERTED.name, INSERTED.type  INTO @InsertOutputFormFields VALUES 
 (@formID, 'products', 'string', 'products'),
 (@formID, 'solutions', 'string', 'solutions'),
 (@formID, 'successes', 'string', 'successes'),
 (@formID, 'resources', 'string', 'resources'),
 (@formID, 'comments', 'string', 'test'),
 (@formID, 'submit', 'string', ''),
 (@formID, 'step', 'string', '430'),
 (@formID, 'skey', 'string', 'SID-04000407-1F158E8D')
;

Next prepare to drop it into a DS XML file with pseudo inline attribute syntax.

DECLARE @formID as varchar(50) DECLARE @InsertOutputForm table (  form_id varchar(50) );DECLARE @InsertOutputFormFields table( id varchar(50), form_id  varchar(50), name  varchar(50),  type varchar(50)); INSERT INTO dbo.forms (name,type,dsuser) OUTPUT    INSERTED.id as form_id  INTO @InsertOutputForm 
VALUES (N'[#formname#partnerinfoform#]', N'[#formtype#form#]',N'[#rde-fields.user#]')
SET @formID=(SELECT form_id FROM @InsertOutputForm); INSERT INTO dbo.form_fields form_id, name, type, data)OUTPUT INSERTED.id, INSERTED.form_id, INSERTED.name, INSERTED.type  INTO @InsertOutputFormFields VALUES 
 (@formID, '[#fieldname#]', '[#fieldtype#string#]', '[#data#]'),
;
<!-- Split Fields -->
		<rde-dm:attribute mode="write" attribute="request:ft.ssv.fields" value="[#request:_sf_form_fieldnames_#].substring(1)" value-separator="|" />
		<rde-dm:attribute mode="write" attribute="request:ft.query.segment1" value="DECLARE @formID as varchar(50) DECLARE @InsertOutputForm table (  form_id varchar(50) );DECLARE @InsertOutputFormFields table( id varchar(50), form_id  varchar(50), name  varchar(50),  type varchar(50)); INSERT INTO dbo.forms (name,type,dsuser) OUTPUT    INSERTED.id as form_id  INTO @InsertOutputForm " />
		<!-- replace pseudo code -->
		<rde-dm:attribute mode="write" attribute="request:ft.query.segment2" value="VALUES (N'[#request:_sf_form_name_#partnerinfoform#]', N'[#request:formtype#form#]',N'[#user:rde-fields.login#anonymous#]') "/>
		<rde-dm:attribute mode="write" attribute="request:ft.query.segment3" value="SET @formID=(SELECT form_id FROM @InsertOutputForm); INSERT INTO dbo.form_fields (form_id, name, type, data) OUTPUT INSERTED.id, INSERTED.form_id, INSERTED.name, INSERTED.type  INTO @InsertOutputFormFields VALUES " />
		
		<!-- Loop Over Fields -->
		<rde-dm:attribute mode="for-each" attribute="request:ft.ssv.fields" alias="field" tag="fields">
			<rde-dm:attribute mode="condition">
				<rde-dm:constraint>context:field NE ""</rde-dm:constraint>
				<field>
					<!-- Get Field Name -->
					<![CDATA[-]]><rde-dm:attribute mode="read" attribute="context:field"/><![CDATA[:]]><rde-dm:attribute mode="read" attribute="request:[#context:field#]" /><![CDATA[;<br/>]]>
					<rde-dm:attribute mode="write" attribute="request:ft.query.segment4" value="[#request:ft.query.segment4#], (@formID, '[#context:field#].replace(';',', ')', '[#fieldtype#string#]', '[#request:[#context:field#badField#]#].replace(';',', ')')"/>
					
					<rde-dm:attribute mode="write" attribute="request:rdb.columns" value="[#request:rdb.columns#], form_fields.[#context:field#]"/>
					<rde-dm:attribute mode="write" attribute="request:rdb.values" value="[#request:rdb.values#],&quot;[#request:[#context:field#badField#]#].replace(';',', ')&quot;"/>
				</field>
			</rde-dm:attribute>
		</rde-dm:attribute>
		
		<rde-dm:attribute mode="write" attribute="request:ft.rdb.formquery" value="[#request:ft.query.segment1#] [#request:ft.query.segment2#] [#request:ft.query.segment3#] [#request:ft.query.segment4#].trim().substring(1);" value-separator="" />
		<![CDATA[<br/>]]>
		<rde-dm:attribute mode="read" attribute="request:ft.rdb.formquery" /><![CDATA[<br/>]]>
		<!-- TODO: 
			- sql query submit
			- validate
			- add date&time of submit
			- set campaign step code = true
			- 
		-->
		<rde-dm:rdb mode="query" alias="otwsm_supplemental" sql="select count(*) as count from dbo.form_fields;"/><![CDATA[-pre count <br/>]]>
		<rde-dm:rdb mode="update" alias="otwsm_supplemental" sql="[#request:ft.rdb.formquery#]"/>
		<rde-dm:rdb mode="query" alias="otwsm_supplemental" sql="select count(*) as count from dbo.form_fields;"/><![CDATA[- post count<br/>]]>

Unfortunately I quickly learned I should have RTFM’d. I ran straight into a blocker.


Unexpected error occurs:This statement isn't allowed=DECLARE @formID as varchar(50) DECLARE @InsertOutputForm table ( form_id varchar(50) )

The RDB DynaMent limits the types of commands that can be executed. Thus I seemed out of luck. Except their is the statement mode which I don’t believe I’ve used before. It will let you run unrestricted SQL statements. If you are running simpler queries do use ‘query’ or ‘update’ to help prevent SQL Injection.

<!-- Split Fields -->
		<rde-dm:attribute mode="write" attribute="request:ft.ssv.fields" value="[#request:_sf_form_fieldnames_#].substring(1)" value-separator="|" />
		<rde-dm:attribute mode="write" attribute="request:ft.query.segment1" value="DECLARE @formID as varchar(50) DECLARE @InsertOutputForm table (  form_id varchar(50) );DECLARE @InsertOutputFormFields table( id varchar(50), form_id  varchar(50), name  varchar(50),  type varchar(50)); INSERT INTO dbo.forms (name,type,dsuser) OUTPUT    INSERTED.id as form_id  INTO @InsertOutputForm " />
		<!-- replace pseudo code -->
		<rde-dm:attribute mode="write" attribute="request:ft.query.segment2" value="VALUES (N'[#request:_sf_form_name_#partnerinfoform#]', N'[#request:formtype#form#]',N'[#user:rde-fields.login#anonymous#]') "/>
		<rde-dm:attribute mode="write" attribute="request:ft.query.segment3" value="SET @formID=(SELECT form_id FROM @InsertOutputForm); INSERT INTO dbo.form_fields (form_id, name, type, data) OUTPUT INSERTED.id, INSERTED.form_id, INSERTED.name, INSERTED.type  INTO @InsertOutputFormFields VALUES " />
		
		<!-- Loop Over Fields -->
		<rde-dm:attribute mode="for-each" attribute="request:ft.ssv.fields" alias="field" tag="fields">
			<rde-dm:attribute mode="condition">
				<rde-dm:constraint>context:field NE ""</rde-dm:constraint>
				<field>
					<!-- Get Field Name -->
					<![CDATA[-]]><rde-dm:attribute mode="read" attribute="context:field"/><![CDATA[:]]><rde-dm:attribute mode="read" attribute="request:[#context:field#]" /><![CDATA[;<br/>]]>
					<rde-dm:attribute mode="write" attribute="request:ft.query.segment4" value="[#request:ft.query.segment4#], (@formID, '[#context:field#].replace(';',', ')', '[#fieldtype#string#]', '[#request:[#context:field#badField#]#].replace(';',', ')')"/>
					
					<rde-dm:attribute mode="write" attribute="request:rdb.columns" value="[#request:rdb.columns#], form_fields.[#context:field#]"/>
					<rde-dm:attribute mode="write" attribute="request:rdb.values" value="[#request:rdb.values#],&quot;[#request:[#context:field#badField#]#].replace(';',', ')&quot;"/>
				</field>
			</rde-dm:attribute>
		</rde-dm:attribute>
		
		<rde-dm:attribute mode="write" attribute="request:ft.rdb.formquery" value="[#request:ft.query.segment1#] [#request:ft.query.segment2#] [#request:ft.query.segment3#] [#request:ft.query.segment4#].trim().substring(1);" value-separator="" />
		<![CDATA[<br/>]]>
		<rde-dm:attribute mode="read" attribute="request:ft.rdb.formquery" /><![CDATA[<br/>]]>
		
		<rde-dm:rdb mode="query" alias="otwsm_supplemental" sql="select count(*) as count from dbo.form_fields;"/><![CDATA[-pre count <br/>]]>
		<rde-dm:rdb mode="statement" alias="otwsm_supplemental" sql="[#request:ft.rdb.formquery#] SELECT id, form_id, name, type FROM @InsertOutputFormFields;"/>
		<rde-dm:rdb mode="query" alias="otwsm_supplemental" sql="select count(*) as count from dbo.form_fields;"/><![CDATA[- post count<br/>]]>
		<!-- TODO: 
			- add date&time of submit
		-->
		<rde-dm:attribute mode="read" attribute="request:step" /><![CDATA[-step<br/>]]>
		<rde-dm:attribute mode="write" op="set" attribute="user:campaign.step[#request:step#]" value="true"/> Set to True
		<rde-dm:attribute mode="read" attribute="user:campaign.step[#request:step#]" /><![CDATA[-step<br/>]]>
	<rde-dm:attribute mode="condition">
		<rde-dm:constraint>(request:redirect-target NE '') AND (request:debug NE 'true')</rde-dm:constraint>
			Redirect Now!<!-- redirect somewhere -->
			<rde-dm:process mode="redirect" type="http" url="http://10.25.0.51/demo/en/partner.htm" >
				<rde-dm:include content="[#request:redirect-target#content/en/index.htm#]"/>
			</rde-dm:process>
	</rde-dm:attribute>

That is the working result of a form handler to work with the “universal” storage tables handling a SmartForm post.

Adventures in Form Handling

In part this week I’m working on a form handling setup. I created a SmartForm and started about writing it’s input to DB via Delivery Server.  I decided to make a more generic handler. We have some tools around for processing (client side validation, server side validation, JCaptcha) but no universal storage mechanism at hand. Conceptually this same processing used in tandem with SmartForm, forms created by dragging and dropping from panels in SmartEdit, a template form, or an external or legacy form. I’ve decided to just store this in a couple of tables as it will allow flexibility to integrate with other applications later on.

Goal:

Create a Delivery Server form handler to store M forms with N form fields per form. (ok I don’t really expect it to scale unlimited)

DDL:

First I created some tables. here they are.

MS SQL 2008 DB Diagram

USE [otwsm_supplemental]
GO

/****** Object:  Table [dbo].[forms]    Script Date: 08/03/2011 09:24:40 ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[forms](
	[id] [uniqueidentifier] ROWGUIDCOL  NOT NULL,
	[name] [nvarchar](50) NOT NULL,
	[type] [nvarchar](50) NOT NULL,
	[dsuser] [nvarchar](50) NOT NULL,
 CONSTRAINT [PK_forms] PRIMARY KEY CLUSTERED
(
	[id] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

ALTER TABLE [dbo].[forms] ADD  CONSTRAINT [DF_forms_id]  DEFAULT (newid()) FOR [id]
GO

ALTER TABLE [dbo].[forms] ADD  CONSTRAINT [DF_forms_type]  DEFAULT (N'form') FOR [type]
GO

ALTER TABLE [dbo].[forms] ADD  CONSTRAINT [DF_forms_user]  DEFAULT (N'anonymous') FOR [dsuser]
GO

USE [otwsm_supplemental]
GO

/****** Object:  Table [dbo].[form_fields]    Script Date: 08/03/2011 09:24:27 ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[form_fields](
	[id] [uniqueidentifier] NOT NULL,
	[form_id] [uniqueidentifier] NOT NULL,
	[name] [nvarchar](50) NULL,
	[type] [nvarchar](50) NULL,
	[data] [nvarchar](max) NULL,
 CONSTRAINT [PK_form_fields] PRIMARY KEY CLUSTERED
(
	[id] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

ALTER TABLE [dbo].[form_fields]  WITH CHECK ADD  CONSTRAINT [FK_form_fields_forms] FOREIGN KEY([form_id])
REFERENCES [dbo].[forms] ([id])
GO

ALTER TABLE [dbo].[form_fields] CHECK CONSTRAINT [FK_form_fields_forms]
GO

ALTER TABLE [dbo].[form_fields] ADD  CONSTRAINT [DF_form_fields_id]  DEFAULT (newid()) FOR [id]
GO

ALTER TABLE [dbo].[form_fields] ADD  CONSTRAINT [DF_form_fields_type]  DEFAULT (N'string') FOR [type]
GO

SQL:

I decided I wanted to insert from a form post with one interaction between DS and SQL.  For now I decided not to make a prepared statement even though they are supported in v10.1.

(If there are better ways to do the insert let me know in the comments)

DECLARE @formID as varchar(50)
DECLARE @InsertOutputForm table

(

  form_id varchar(50)

);

DECLARE @InsertOutputFormFields table

(

  id varchar(50),
  form_id  varchar(50),
  name  varchar(50),
  type varchar(50)

);

INSERT INTO dbo.forms (name,type,dsuser)

OUTPUT

    INSERTED.id as form_id

  INTO @InsertOutputForm

VALUES (N'test', N'partnerinfoform',N'cindy')

SET @formID=(SELECT form_id FROM @InsertOutputForm);

INSERT INTO dbo.form_fields (form_id, name, type, data)

OUTPUT

    INSERTED.id, INSERTED.form_id, INSERTED.name, INSERTED.type

  INTO @InsertOutputFormFields

 VALUES (@formID, 'products', 'string', 'products'),
(@formID, 'solutions', 'string', 'solutions'),
(@formID, 'successes', 'string', 'successes'),
(@formID, 'resources', 'string', 'resources'),
(@formID, 'comments', 'string', 'test'),
(@formID, 'submit', 'string', ''),
(@formID, 'step', 'string', '430'),
(@formID, 'skey', 'string', 'SID-04000407-1F158E8D');

SELECT id, form_id, name, type FROM @InsertOutputFormFields;

RESULTS:

After struggles with getting to know the output clause and the proper variable syntax I finally got MS SQL 2008 to do all the work.

MS SQL 2008 result set
Query Result

Next step. Writing finishing DS page logic to format the query.