tag:blogger.com,1999:blog-32632110215680274492024-03-14T14:41:36.203+08:00Rob on EKPTips, tricks and FAQs on NetDimensions LMS (formerly Enterprise Knowledge Platform) — a blog by <a href="https://blog.rmlowe.com/">Robert Lowe</a>Robert Lowehttp://www.blogger.com/profile/16675118101660507438noreply@blogger.comBlogger57125tag:blogger.com,1999:blog-3263211021568027449.post-26104002579007234892010-05-26T01:07:00.007+08:002010-07-28T14:07:03.628+08:00Can I delegate authentication by embedding an EKP login form directly in my portal?<p>Portals developed using the <a href="http://www.netdimensions.com/products/portal-toolkit.php">EKP Portal Toolkit</a> typically use a process called <em>delegated authentication</em> that makes it possible for EKP to authenticate the user on behalf of the portal and thus avoid the problem of multiple logins.</p>
<p>In such cases, NetDimensions recommends including a <em>Sign In</em> link in the upper right corner of each portal page. Clicking the link initiates the delegated authentication process, which involves redirecting the user to EKP. If, as is typical, the user is not signed into EKP, he or she will be presented with the EKP login page, and will need to log into EKP before being redirected back to the portal with an appropriate authentication token. However, it is possible that the user might already be signed into EKP, in which case he or she will be immediately and transparently redirected back to the portal with the authentication token. This could happen for a number of reasons: the user's portal session might have timed out while his or her EKP session has not (which could happen if the session timeout for the portal is shorter than the session timeout for EKP); the user might previously have logged into EKP directly; or the user might already have signed into another portal running against the same EKP site.</p>
<p>The above is the approach followed in our <a href="https://wiki.netdimensions.com/confluence/display/products/General+API+Code+Samples">code samples</a> and by our <a href="https://wiki.netdimensions.com/confluence/display/products/List+of+Portal+Demo+Sites">portal demo sites</a>.</p>
<p>Portal developers sometimes ask us if they can embed an EKP login form directly in their portal instead of using the sign-in link as described above. While this is possible, it creates a suboptimal user experience because it will in some cases force users to enter their user ID and password unnecessarily. This is because the only way for the portal to determine whether the user is already signed into EKP (which would allow them to bypass the login process as described above) is to redirect the user to EKP. However if the login form is embedded in the portal then this redirect does not occur until <strong>after</strong> the credentials have been entered. In other words, by the time we can determine whether the user's credentials are required, they will already have been entered, perhaps unnecessarily.</p>
<p>This situation might be acceptable if there is only a single portal, and if users of the portal site are unlikely to also access EKP directly. It becomes increasingly undesirable as the number of portals increases.</p>
<p>With the above caveats, here is an outline of how an embedded login form could be implemented if desired.</p>
<p>First, create a <a href="https://wiki.netdimensions.com/confluence/download/attachments/97/EN012+EKP+Login+Form.pdf?version=1&modificationDate=1245895355747">login form</a> with the following fields:
<ul>
<li>A hidden field named <code>TX</code> with value <code>VERIFY</code>;</li>
<li>A text field named <code>UID</code> for the user ID;</li>
<li>A password field named <code>PWD</code> for the user's password; and</li>
<li>A hidden field named <code>target</code>, the value of which should be the absolute URL of a “post-login” callback page that is part of the portal, the purpose of which is described below.</li>
</ul>
Be sure that the form uses the <code>POST</code> method to ensure that the password is not included in the URL when the form is submitted. Note that there is no need to include the delegated authentication <code>nonce</code> or <code>callback</code> parameters in this form, since the handler will not use them.</p>
<p>Assuming that the base URL of the EKP site is <code>https://www.example.com/ekp/</code> and the URL of the callback page is <code>http://portal.example.com/post_login.php</code>, this login form might look as shown below.</p>
<pre class="code"><form method="post" action="https://www.example.com/ekp/servlet/ekp">
<input type="hidden" name="TX" value="VERIFY">
<p><label>User ID: <input type="text" name="UID"></label></p>
<p><label>Password: <input type="password" name="PWD"></label></p>
<input type="hidden" name="target" value="http://portal.example.com/post_login.php">
<p><input type="submit" value="Sign In"></p>
</form></pre>
<p>When the user submits the login form (and assuming he or she entered a valid user ID and password), he or she will be signed into EKP and then redirected to the “post-login” portal page. (If the user entered an incorrect user ID or password EKP will display the standard login page so that he or she can retry.) At this point, though, the portal has not validated that the login was successful. (The fact that the user has been redirected to the post-login page is not itself sufficient, since the portal can't reliably determine that the user didn't navigate to this page manually.)</p>
<p>At this point, the portal should initiate the delegated authentication process in the normal way, i.e. by generating a random nonce value, storing it in the session, and then redirecting the user to <code>https://www.example.com/ekp/servlet/ekp/authenticate?nonce=<strong>{nonce-value}</strong>&callback=<strong>{callback-value}</strong></code>. Note that, since the user is already signed into EKP, he or she will not be shown the login page but will instead immediately be redirected to the callback URL with the authentication token, which the portal can then use to validate the login. In other words, this second step should be completely transparent to the user but allows the portal to verify that the login was successful.</p>Robert Lowehttp://www.blogger.com/profile/16675118101660507438noreply@blogger.com4tag:blogger.com,1999:blog-3263211021568027449.post-44000225648331039332010-04-23T00:11:00.004+08:002010-08-14T18:39:52.072+08:00What's New in the EKP 6.1 API<p>Although there are no major new areas of functionality in the EKP 6.1 API, there are several useful enhancements, listed herewith.<p>
<ul>
<li><strong>News feeds can now be filtered by category.</strong> Both <code>api/publicNews</code> and <code>api/userNews</code> now support an optional <code>categoryId</code> parameter. <strong>Why this is useful:</strong> If you are creating a topical portal, you can now restrict the articles shown to those that are relevant to the subject of the portal; for example, an onboarding portal can show articles targeted to the user, with the further restriction that only articles related to onboarding are shown.</li>
<li><strong><code>api/module</code> returns additional information.</strong> Specifically, module attributes are now returned, as are prices (currency and amount) for module sessions. (Since module attributes are not always intended for learners, they will be returned only if you authenticate as a System Administrator.) <strong>Why this is useful:</strong> Price information is essential for e-commerce portals, while module attributes can be used to retrieve implementation-specific data such as a product code to be passed to a payment gateway (if this is not the same as the EKP course ID).</li>
<li><strong>Images specified via the <em>Picture URL</em> field are now <a href="http://www.robonekp.com/2010/04/images-in-news-feeds.html">available</a> in news feeds.</strong> (Note that you can always include inline images in your articles using the HTML <code><img></code> tag.) <strong>Why this is useful:</strong> Images add impact to news articles, and being able to include those images on your portal sites will make those sites more engaging.</li>
</ul>
<p>We hope these enhancements will make the API significantly more useful in EKP 6.1.</p>Robert Lowehttp://www.blogger.com/profile/16675118101660507438noreply@blogger.com0tag:blogger.com,1999:blog-3263211021568027449.post-31408578127294234422010-04-05T14:14:00.008+08:002011-03-04T20:51:03.946+08:00Images in News Feeds<p>EKP's <a href="http://www.netdimensions.com/products/portal-toolkit.php">API</a> provides two functions for retrieving feeds of news articles in either RSS 2.0 or Atom 1.0 format. <code>publicNews</code> returns a feed of public news articles (the same articles that would appear on the login page), while <code>userNews</code> returns the personalized news feed for a specific user.</p>
<p>Previously, these feeds did not include any values for the <strong>Picture URL</strong> field configured for the articles (although they would include images included directly in the text of the articles using HTML markup). However, starting from build 6.0.0.127, these values will be included in feeds when using either the <code>rssfull</code> or <code>atom</code> format. (<strong>Picture URL</strong> values are not included when using the <code>rssteasers</code> format.)</p>
<p>Since neither RSS nor Atom include a distinct field for images, the value is included as an entity-escaped HTML <code>img</code> element inside the <code>description</code> element (for <code>rssfull</code>) or the <code>content</code> element (for <code>atom</code>). The unescaped <code>img</code> element would look similar to the example below.</p>
<pre class="code"><img alt='My Article'
class='news-image'
src='/ekp/nd/fresco/repository/myimage.jpg'></pre>
<p>When the <code>img</code> element is entity-escaped and included in a <code>description</code> element (for the <code>rssfull</code> format), the result would look as shown below.</p>
<pre class="code"><description>&lt;img alt='My Article' class='news-image'
src='/ekp/nd/fresco/repository/myimage.jpg'&gt; This is the text of the
article.</description></pre>
<p>The equivalent <code>content</code> element for the <code>atom</code> format is shown below.</p>
<pre class="code"><content type="html">&lt;img alt='My Article' class='news-image'
src='/ekp/nd/fresco/repository/myimage.jpg'&gt; This is the text of the
article.</description></pre>
<p>Note that the <code>img</code> element includes an attribute <code>class='news-image'</code>, which you can take advantage of to apply special styling to these images using CSS if you are including the article text in your own Web pages. For example, the rule below ensures that all such images are floated to the right of the article text.</p>
<pre class="code">img.news-image {float: right;}</pre>
<p>The rule below ensures that the images are <strong>not</strong> displayed.</p>
<pre class="code">img.news-image {display: none;}</pre>
<p>If you want to use the image URL in a way that can't easily be accomplished using CSS, you could extract the URL, for example by parsing the article text into its own DOM and then applying the following XPath expression.</p>
<pre class="code">//img[@class="news-image"]/@src</pre>
<p>We know that images can add significant impact to your content, and we hope that this change makes it easier for you to use news article images in your portal pages.</p>Robert Lowehttp://www.blogger.com/profile/16675118101660507438noreply@blogger.com0tag:blogger.com,1999:blog-3263211021568027449.post-68294199351573554042010-03-30T20:57:00.006+08:002010-03-31T20:39:17.939+08:00Can I create a custom search form that displays the search results enclosed in navigation frames?<p>I've <a href="http://www.robonekp.com/2007/02/how-can-i-link-directly-to-page-in-ekp.html">written before</a> about how to link to an EKP page in such a way that the navigation frames are retained. The technique described in that post works so long as the exact URL of the target page (i.e. the page that will be enclosed in the navigation frames) is known in advance.</p>
<p>However, what if you want to provide access to a set of pages for which the exact URLs cannot be known in advance because they depend on input from the user? In particular, what if you want to provide a custom search form that displays the search results enclosed in the navigation frames?</p>
<p>In this case the technique described in the previous post won't work as-is because the exact URL depends on the user's search query. For example, if the user searches for <em>bacon</em> then the URL of the search results page will be as shown below.</p>
<pre class="code">/ekp/servlet/ekp?TX=FRAMELESSCATALOGSEARCH&KEYW=<strong><em>bacon</em></strong></pre>
<p>With a little JavaScript ingenuity we can achieve the desired effect. The trick is to add an <code>onsubmit</code> handler to the search form that takes the query and uses it to construct the URL of the target page, which it then uses to populate another, hidden, form field. This requires a JavaScript function contained in a <code>script</code> element that we will need to include in the <code>head</code> of the page's HTML source.</p>
<pre class="code"><script type="text/javascript">
function setMainSrc() {
document.searchform.mainSrc.value
= "/ekp/servlet/ekp?TX=FRAMELESSCATALOGSEARCH&KEYW="
+ encodeURIComponent(document.searchform.KEYW.value);
}
</script></pre>
<p>Below is the form itself, including both <code>onsubmit</code> handler and hidden field.</p>
<pre class="code"><form action="/ekp/servlet/ekp/pageLayout"
name="searchform"
onsubmit="setMainSrc(); return true;">
<input name="KEYW" type="text">
<input name="mainSrc" type="hidden">
<input type="submit" value="Search">
</form></pre>
<p>When the user enters a query and clicks the <em>Search</em> button, he or she will see EKP's search results page enclosed in the standard navigation frames, even though the original page did not include any frames.</p>Robert Lowehttp://www.blogger.com/profile/16675118101660507438noreply@blogger.com0tag:blogger.com,1999:blog-3263211021568027449.post-20666429582514621032010-03-09T21:28:00.004+08:002010-08-14T18:34:56.677+08:00How can I hide the Logout link that appears as part of EKP's navigation controls?<p>By default EKP displays a <em>Logout</em> link as part of its navigation controls. In some cases it might be desirable to hide this link, in particular if EKP is being used in conjunction with a single sign-on solution in which it is not desired to provide application-specific sign-out functions.</p>
<p>This post describes a simple way to hide the <em>Logout</em> link using a Cascading Style Sheets (CSS) rule. Because it is CSS-based, this technique can be applied to specific skins, such that the <em>Logout</em> link appears only for specific groups of users depending on their skin setting.</p>
<p>The technique relies on the fact that, in most skins, the <em>Logout</em> link is the only element with both a <code>class='sec-links'</code> attribute and a <code>target='_top'</code> attribute. Therefore, applying the CSS rule shown below has the effect of hiding the <em>Logout</em> link without affecting other navigation links.</p>
<pre class="code">.sec-links[target="_top"] {display: none;}</pre>
<p>For skins in which the <em>Logout</em> link appears in the left frame (or the right frame for right-to-left skins), this rule should be added to the file named <code>left.css</code> inside the skin directory. For example, for the <em>EKP-60</em> skin the rule would be added to the file named <code>left.css</code> inside the <code>nd/fresco/styles/EKP-60/</code> directory. For skins in which the <em>Logout</em> link appears in the top frame, this rule should be added to the file named <code>top.css</code> inside the skin directory.</p>
<p>This technique should work for most recent skins. It might not work for certain older skins, in particular those that use images for button labels.</p>Robert Lowehttp://www.blogger.com/profile/16675118101660507438noreply@blogger.com2tag:blogger.com,1999:blog-3263211021568027449.post-47000035387238121162010-03-08T21:39:00.006+08:002010-03-08T22:36:31.107+08:00Creating a custom sign-up form using PHP<p>EKP provides a <strong>built-in sign-up (self-registration) function</strong> for new users. However, there are situations in which you might want to provide a custom sign-up form, particularly if users are accessing EKP's functionality via a portal.</p>
<ul>
<li>A custom sign-up form provides you with <strong>complete control of the “look and feel”</strong> of your sign-up page.</li>
<li>A custom sign-up form enables you to <strong>control what happens at the end of the sign-up process</strong>. For example, instead of directing users to a standard EKP start page, you can direct them to a page within a learning portal.</li>
</ul>
<p>This post outlines how you might go about creating a custom sign-up page using PHP.</p>
<p>The first step is to create the sign-up form itself. This might reside in a file named <em>signup.php</em> for example.</p>
<pre class="code"><form action="handlesignup.php" method="POST">
User ID: <input name="userid" type="text" maxlength="85">
<br>
Password: <input name="password" type="password">
<br>
Family Name: <input name="familyname" type="text" maxlength="85">
<br>
Given Name: <input name="givenname" type="text" maxlength="85">
<br>
<input type="submit" value="Sign Up">
</form></pre>
<p>When user submits this form, the browser will send an HTTP POST request to another PHP script named <em>handlesignup.php</em>. The example below shows what the code in this file might look like.</p>
<pre class="code"><?php
// defines $ekp_base, $auth_key
require 'config.php';
// Grab the parameters from the request
$user_id = $_POST['userid'];
$password = $_POST['password'];
$family_name = $_POST['familyname'];
$given_name = $_POST['givenname'];
// Other fields as needed...
// Format as CSV for the contentHandler/usersCsv API function
// CSV data consists of two lines: one for headers, and one for data
// Full list of permitted fields is same as for User Data Loader
// Note that almost all fields are optional
$data = '"Action","UserID","Password","FamilyName","GivenName"' . "\r\n"
. '"A","' . $user_id . '","' . $password . '","' . $family_name . '","'
. $given_name . '"';
// Use cURL to POST the CSV data to EKP
// Note that the profile parameter is optional
$ch = curl_init($ekp_base . 'contentHandler/usersCsv?profile=signupprofile');
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-Type: text/csv"));
// Although EKP ignores the user name, a non-empty value must be used
// otherwise cURL will not include the authentication header
$user_name = "dummy";
curl_setopt($ch, CURLOPT_USERPWD, $user_name . ":" . $auth_key);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
$result = curl_exec($ch);
curl_close($ch);
// Now show a confirmation page to the user, or redirect him or her to an
// appropriate page.
?>
<html>
<head>
<title>Sign-Up Completed</title>
</head>
<body>
<h1>Thanks for signing up!</h1>
</body>
</html></pre>
<p><strong>Known issues</strong></p>
<ul>
<li>The code above does not attempt to escape the double quotation mark character ("), which has a special meaning in CSV, when it occurs in field values. In order to guard against possible data-injection attacks, the code should really replace each occurrence of this character with two instances of the character.</li>
<li>The code does not perform any validation of the input. A more robust example would validate the user input before calling the API function.</li>
<li>As a special case of the above, it is common on sign-up forms to ask the user to re-type his or her password, since a mistyped password might prevent the user from accessing his or her account. The example code shown here does not do this.</li>
</ul>Robert Lowehttp://www.blogger.com/profile/16675118101660507438noreply@blogger.com1tag:blogger.com,1999:blog-3263211021568027449.post-72824971309565560792009-06-17T03:10:00.011+08:002009-06-18T00:55:36.068+08:00Publishing a podcast using the News Manager<p><strong>EKP 5.6 provides RSS feeds</strong> both for public news on the login page and for personalized news on a learner's home page. (Note: RSS feeds are available only if your license includes <a href="http://www.robonekp.com/2008/03/api-101.html">API</a> access.)</p>
<img style="display:block; margin:0px auto 10px; text-align:center;width: 287px; height: 120px;" src="http://1.bp.blogspot.com/_f7breNCi1sQ/SjgN56G_pHI/AAAAAAAABMM/YYcEQABOm5Q/s400/RSS+feed.png" border="0" alt="RSS feed link on Home Page" id="BLOGGER_PHOTO_ID_5348039846004761714" />
<p>In EKP 5.6.0.129 <strong>we have added support for <a href="http://en.wikipedia.org/wiki/RSS_enclosure">RSS enclosures</a></strong>. Roughly speaking, RSS enclosures serve the same purpose as email attachments—they make is possible to attach a file of arbitrary type to a text message. Probably the most interesting consequence of this change is that <strong>it's now possible to use the News Manager to publish a podcast</strong>. Here's how.</p>
<ol>
<li><p><strong>Record a podcast episode</strong>, edit it as necessary, and save as an appropriate media file (audio or video). There are many tools available for recording, editing and encoding media files. For audio, we suggest <a href="http://audacity.sourceforge.net/">Audacity</a> for recording and editing, with the <a href="http://lame.buanzo.com.ar/">LAME MP3 encoder</a> to generate MP3 files.</p></li>
<li><p><strong>Upload your media file</strong> to the Repository Manager.</p><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_f7breNCi1sQ/Sjj_suU-7DI/AAAAAAAABMU/F9SR6By7iJk/s1600-h/Repository+Manager.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 325px;" src="http://3.bp.blogspot.com/_f7breNCi1sQ/Sjj_suU-7DI/AAAAAAAABMU/F9SR6By7iJk/s400/Repository+Manager.png" border="0" alt="Uploading an MP3 file to the Repository Manager" id="BLOGGER_PHOTO_ID_5348305701317831730" /></a></li>
<li><p><strong>Create a news article</strong> in the News Manager. For the <strong>File Attachment URL</strong> field, select the media file you uploaded in the previous step. Make sure the article is targeted to the appropriate audience, and publish as usual.</p><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_f7breNCi1sQ/SjkCGxiqfyI/AAAAAAAABMc/biw2NTyC7WA/s1600-h/Create-Edit+News+Article.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 260px;" src="http://1.bp.blogspot.com/_f7breNCi1sQ/SjkCGxiqfyI/AAAAAAAABMc/biw2NTyC7WA/s400/Create-Edit+News+Article.png" border="0" alt="Creating a news article with attached MP3 file" id="BLOGGER_PHOTO_ID_5348308347880374050" /></a></li>
<li><p><strong>A listener/viewer can subscribe to an appropriate RSS feed</strong> using a “podcatcher” such as Apple's <a href="http://www.apple.com/itunes/">iTunes</a>. The podcatcher will download new podcast episodes as they are made available. The episodes can then be played offline, and can also be synchronized to devices such as portable MP3 players and phones.</p><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_f7breNCi1sQ/SjkeMjsA1kI/AAAAAAAABMk/gT4r1AhQFuU/s1600-h/iTunes.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 215px;" src="http://2.bp.blogspot.com/_f7breNCi1sQ/SjkeMjsA1kI/AAAAAAAABMk/gT4r1AhQFuU/s400/iTunes.png" border="0" alt="Subscribed podcast in iTunes" id="BLOGGER_PHOTO_ID_5348339233566283330" /></a></li>
</ol>Robert Lowehttp://www.blogger.com/profile/16675118101660507438noreply@blogger.com0tag:blogger.com,1999:blog-3263211021568027449.post-2221227082941144262009-06-05T20:37:00.009+08:002009-06-05T21:56:44.990+08:00The API Explorer<p><strong>EKP provides an <a href="http://www.robonekp.com/2008/03/api-101.html">application programming interface (API)</a></strong> that enables other applications and Web sites to interact with EKP—for example, to create user accounts and enrollments, and to obtain information about courses, catalogs, enrollments and training records.</p>
<p>We've always provided API documentation as part of the EKP distribution. However, in working with software developers, we've identified a couple of ways in which the documentation could be improved.</p>
<p>Firstly, <strong>the API changes between releases</strong> as new API functions are added. This means it's important to work with the correct version of the documentation for the EKP version you're developing for. However, keeping track of different document versions is a hassle.</p>
<p>Secondly, <strong>most developers learn more easily if they are able to call API functions interactively</strong> and generate actual responses, rather than simply reading dry documentation. However, calling API functions generally involves writing code.</p>
<p>With these points in mind, in EKP 5.6 we introduced the <strong>API Explorer</strong>. The API Explorer serves two purposes: it is both <strong>an online reference</strong> for the API functions, and <strong>a tool that enables developers to learn about the API by calling its functions interactively</strong>.</p>
<a href="http://utest2.netdimensions.com/utest/api/"><img style="display:block; margin:0px auto 10px; text-align:center;width: 400px; height: 254px;" src="http://2.bp.blogspot.com/_f7breNCi1sQ/SikZz7Yr3wI/AAAAAAAABJU/v9vkh9ojLtA/s400/Enterprise+Knowledge+Platform+API.png" border="0" alt="Enterprise Knowledge Platform API" id="BLOGGER_PHOTO_ID_5343830812757057282" /></a>
<p><strong>The API Explorer lists the API functions available.</strong> For each function, it provides information about how to invoke the function, including the URL and the expected HTTP method, parameters and authentication scheme. Where possible, it provides one or more HTML forms that can be used to call the API function directly from the browser. (Note: some API functions cannot be invoked from a browser.)</p>
<a href="http://utest2.netdimensions.com/utest/api/#NEWS_ACTIONS:userNews"><img style="display:block; margin:0px auto 10px; text-align:center;width: 400px; height: 301px;" src="http://3.bp.blogspot.com/_f7breNCi1sQ/Sikbajrt9eI/AAAAAAAABJc/jk_eoPLF8dg/s400/userNews.png" border="0" alt="userNews API function" id="BLOGGER_PHOTO_ID_5343832575920960994" /></a>
<p><strong>The API Explorer can be accessed by appending <i>api/</i> to the base URL of your EKP site.</strong> For example, if your site is located at <i>http://www.example.com/ekp/</i>, then the API Explorer for the site can be accessed at <i>http://www.example.com/ekp/api/</i>. At the time of writing, an instance of the API Explorer is available at <i><a href="http://utest2.netdimensions.com/utest/api/">http://utest2.netdimensions.com/utest/api/</a></i>.</p>
<p>Note: If you do not see the API Explorer at the expected URL, your Web server (e.g. Apache or IIS) might not be forwarding the requests to Tomcat. In the case of Apache, you can fix this by adding a directive like the one below to <i>httpd.conf</i>.</p>
<pre class="code">JkMount /ekp/api/* ajp13</pre>Robert Lowehttp://www.blogger.com/profile/16675118101660507438noreply@blogger.com1tag:blogger.com,1999:blog-3263211021568027449.post-9617920492905967372009-06-03T04:29:00.004+08:002009-06-03T21:37:59.987+08:00Publishing a new course revision to learners who have already completed an earlier revision<p>EKP 5.5 introduced the ability to create new revisions of existing courses. This simplified the process of updating an existing course, and made it possible to keep track of which version of a course a learner was taking or had completed.</p>
<p>When you created a new revision of a course, EKP would automatically make the new revision available to learners who were enrolled in, but had not yet started, an earlier revision. EKP also provided the option to make the new revision available to learners who had already started, but had not yet completed, an earlier revision.</p>
<p>Since we released support for course revisions, several of you have told us that you also needed to be able to make new revisions automatically available to users who had already taken and completed an earlier revision of the course, so that those learners would see the latest content if they reviewed the completed course. So, as of EKP 5.6.0.122, there is a third option when creating a new course revision, which publishes the new revision to all learners, including those who have already completed an earlier revision.</p>
<img style="display:block;margin:0px auto 10px; text-align:center;width: 400px; height: 172px;" src="http://2.bp.blogspot.com/_f7breNCi1sQ/SiWNwwhTqZI/AAAAAAAABIE/ZwCaTBE0yoc/s400/Confirm+New+Course+Revision.png" border="0" alt="Confirm New Course Revision" id="BLOGGER_PHOTO_ID_5342832401742801298" />
<p>We hope this makes course revisions in EKP even more useful.</p>Robert Lowehttp://www.blogger.com/profile/16675118101660507438noreply@blogger.com1tag:blogger.com,1999:blog-3263211021568027449.post-60860775341720425022009-05-26T13:58:00.010+08:002009-06-19T10:43:17.129+08:00How to bulk import courses that do not have distinct identifiers in their course descriptions<p>EKP features bulk import utilities for both <a href="http://www.robonekp.com/2007/04/batch-import-of-scormims-content.html">SCORM</a> and <a href="http://www.robonekp.com/2007/02/improvements-to-bulk-courseware-import.html">AICC</a> content. Using these utilities, you can package multiple courses into a zip file which you can then upload. EKP will create online modules based on the course descriptions in the zip file. In addition to information on how to launch and track the courses, EKP will extract descriptive information such as course titles, descriptions, objectives and so on. This is extremely handy if you need to import catalogs of hundreds or even thousands of courses.</p>
<p>Previously, the ID fields that EKP would use for the new modules would also be extracted from the course description files. While this usually works well, it fails if the course description files contain duplicate identifiers, since each module in EKP needs to have a distinct ID. This might happen for example if the course description files were created by copying a template and making only minimal changes.</p>
<p>Starting from version 5.6.0.119, the bulk import utilities provide an option to generate module IDs based on the file names of the imported items instead of the identifiers contained within course descriptions themselves. This provides an easy way to import large numbers of courses even if the identifiers within the course descriptions are not distinct.</p>
<img style="display:block;margin:0px auto 10px; text-align:center;width: 400px; height: 92px;" src="http://1.bp.blogspot.com/_f7breNCi1sQ/ShuLcZCitNI/AAAAAAAABGE/jYPNLQyQRUI/s400/How+do+you+want+the+system+to+select+IDs+for+the+courses+in+this+package.png" border="0" alt="How do you want the system to select IDs for the courses in this package? * Use IDs specified in the course descriptions * Use the base file names of the source files" id="BLOGGER_PHOTO_ID_5340015103052002514" />
<p>The default is to use the identifiers specified in the course description files, as before. If you choose to generate IDs based on the file names and the items being imported are content packages, the IDs used will be the names of the “inner” zip files, without the <i>.zip</i> extension. If the items being imported are sets of AICC course structure files, the IDs used will be the base file names—for example, if one of the sets uses the file names <i>course1.au</i>, <i>course1.crs</i>, <i>course1.cst</i> and <i>course1.des</i>, then the ID used will be <code>course1</code>.</p>
<p>We know that many of you rely on the bulk import utilities to set up and updates your course catalogs, and we hope that this change makes them even more useful.</p>Robert Lowehttp://www.blogger.com/profile/16675118101660507438noreply@blogger.com0tag:blogger.com,1999:blog-3263211021568027449.post-86324201338615260042008-10-02T14:00:00.002+08:002009-06-05T21:15:33.091+08:00PENS authentication<p>I've written before about <a href="http://www.robonekp.com/2007/02/how-can-i-use-pens-to-enable-one-click.html">how to use a PENS-conformant authoring tool or LCMS with EKP</a>. However, in that post I didn't fully explain the intricacies of how EKP handles authentication of PENS requests.</p>
<p>EKP actually provides <strong>two</strong> PENS endpoints, which differ only in how they handle authentication. Assuming your installation uses the default <code>/ekp/</code> path prefix, the endpoint URLs are as follows.</p>
<pre class="code">/ekp/pens
/ekp/servlet/ekp/pens</pre>
<p>The first of these endpoints handles authentication exactly as described in the <a href="http://www.aicc.org/docs/tech/cmi010v1a.pdf" rel="nofollow">PENS specification</a>. That is, the PENS request is expected to include parameters named <code>system-user-id</code> and <code>system-password</code>, the values of which should correspond with, respectively, the user ID and password of an EKP account with permission to create courses. (In fact, this endpoint will also accept credentials supplied as an <a href="http://en.wikipedia.org/wiki/Basic_access_authentication" rel="nofollow">HTTP basic authentication</a> header. This is allowed primarily for consistency with other EKP web services. However, the request parameters always take precedence when supplied.)</p>
<p>The second endpoint ignores the PENS authentication request parameters and HTTP basic authentication headers, and instead uses EKP's standard login sessions for authentication. While this is strictly outside the scope of the PENS specification, it is convenient in situations where the PENS request originates directly from a browser. Since the user is likely to have already logged into EKP at the time at which the PENS request is sent, this approach avoids the need for the user to re-enter his or her credentials every he or she initiates the PENS publishing process.</p>Robert Lowehttp://www.blogger.com/profile/16675118101660507438noreply@blogger.com0tag:blogger.com,1999:blog-3263211021568027449.post-54177164398783281092008-09-01T20:20:00.004+08:002010-10-28T19:48:19.199+08:00User accounts and user statuses<p>Every user account in EKP has a <em>Current Status</em> field which, as of EKP 5.5, can take one of seven possible values. Herewith, a list of the possible values, and an explanation of the usage of each.</p>
<dl>
<dt>Active</dt>
<dd>The account is available for login.</dd>
<dt>Suspended</dt>
<dd>The account is not currently available for login, but is expected to be reactivated in the future. An account can be marked as <em>Suspended</em> by an administrator, or automatically as a result of too many failed logins.</dd>
<dt>Account Closed</dt>
<dd>The account is not available for login, and is not expected to be reactivated in the future. However, the account will still be viewable using review and administrative functions.</dd>
<dt>Logically Deleted</dt>
<dd>The account is not available for login, and is not expected to be reactivated in the future. Moreover, the account will be hidden in most parts of the system aside from the User Editor.</dd>
<dt>Self registration user pend for approval</dt>
<dd>The account was created by a user self-registering with the system. It is awaiting approval and activation by an administrator.</dd>
<dt>Locked</dt>
<dd>The account is not currently available for login, but might be reactivated in the future. Similar to <em>Suspended</em>. If specified under <em>System Configuration</em>, accounts are automatically marked as <em>Locked</em> after a period of inactivity.</dd>
<dt>User Account/Records Migrated</dt>
<dd>Used to indicate that records that were previously associated with this account have been migrated to a different account. The account is effectively obsolete, but has not yet been deleted.</dd>
</dl>
<p><i>[This post was updated on October 28, 2010 to remove obsolete licensing information.]</i></p>Robert Lowehttp://www.blogger.com/profile/16675118101660507438noreply@blogger.com0tag:blogger.com,1999:blog-3263211021568027449.post-34667496054916612822008-07-09T04:34:00.002+08:002008-07-09T05:21:05.079+08:00Desktop publishing tools and PENS<p>I've written before about <a href="http://www.robonekp.com/2007/02/how-can-i-use-pens-to-enable-one-click.html">how PENS can be used to enable one-click publishing from a publishing system (authoring tool or LCMS)</a>. Herewith, some notes on using PENS with desktop, as opposed to web-based, tools.</p>
<p>PENS requires that, when a publishing system sends a request to the LMS to “collect” a content package, it supplies a URL (typically using one of <em>http</em>, <em>https</em> or <em>ftp</em> for the URL scheme) from which the package can be collected. If the publishing system is web-based, then this URL will typically refer back to the publishing system itself.</p>
<p>For a desktop publishing system, this requirement effectively means that there needs to be a server, separate from the desktop software, to which the content can be uploaded prior to being collected by the LMS. Typically this will be an FTP server, although it could in principle be a web server.</p>
<p>Ideally, the desktop software will enable the user to configure details of this FTP server (including host, user name and password) in addition to the LMS details. When a course is published, the software should perform the following actions:</p>
<ul>
<li>package the content into a zip file;</li>
<li>upload the content package to the FTP server using the configured host, user name and password; and</li>
<li>send a PENS request to the LMS instructing it to collect the content package from the FTP server.</li>
</ul>
<p>In principle, it would be possible to manually upload the content package to the FTP server if the desktop software does not do this automatically. In practice, though, this really defeats the purpose of using PENS, since it would be simpler to create a SCORM or AICC content package on the desktop machine, and then upload the package directly to the LMS.</p>
<p>Another theoretical possibility would be to run a web or FTP server on the desktop machine itself. However, this would require that the desktop machine has a stable IP address and/or host name that the LMS can call back to, and that HTTP or FTP requests to the desktop machine are not blocked by hardware or software firewalls. In other words, it's almost guaranteed <strong>not</strong> to work in practice.</p>
<p>In summary, a desktop publishing system can theoretically “support” PENS without also supporting the automatic FTP upload step. However, without this automatic FTP upload, PENS is unlikely to provide any practical benefit.</p>Robert Lowehttp://www.blogger.com/profile/16675118101660507438noreply@blogger.com0tag:blogger.com,1999:blog-3263211021568027449.post-21785481722365111512008-07-09T02:51:00.004+08:002008-07-09T03:42:25.541+08:00Sun Java Plug-in bug and SCORM communication<p>A few users have encountered the following issue that affects tracking of SCORM courses on a small number of EKP sites.</p>
<p><strong>Symptoms:</strong> SCORM courses fail to communicate with EKP, and one or more messages like the one below appear in the browser's Java console.</p>
<pre class="code">java.security.AccessControlException: access denied (java.net.SocketPermission www.example.com:80 connect,resolve)</pre>
<p><strong>Analysis:</strong> This issue is caused by a <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6669818" rel="nofollow">bug in certain versions of Sun's Java Plug-in</a>. The bug was introduced in Java 6 Update 3, and is fixed in Java 6 Update 10. It affects Firefox and Safari; Internet Explorer is not affected.</p>
<p>Note that this problem does <strong>not</strong> affect all sites. It appears to affect a small proportion of sites, and is related to the site's DNS configuration.</p>
<p><strong>Solution:</strong> Users who encounter the problem described above can apply one of the following solutions.</p>
<ul>
<li>Ensure that your users are using a version of the Sun Java Plug-in that is not affected by this problem. Users who currently have Java 6 Update 3 or above can upgrade to Java 6 Update 10. (Note that, as of 9 July 2008, Update 10 is flagged as “beta.” However, it is the first version offered on the <a href="http://java.sun.com/javase/downloads/index.jsp" rel="nofollow">downloads page</a>.)</li>
<li>Another solution that appears to be effective is to ensure that each EKP domain (e.g. <em>ekp.example.com</em>) is mapped (via DNS) to a unique IP address. That is, where multiple EKP instances are located on a single server, ensure that each instance on the server is associated with a different IP address.</li>
</ul>Robert Lowehttp://www.blogger.com/profile/16675118101660507438noreply@blogger.com0tag:blogger.com,1999:blog-3263211021568027449.post-55607939687741799282008-06-15T23:53:00.024+08:002010-05-28T16:54:01.744+08:00When do I need to use the Content Server Configuration settings in EKP Gold?<p>EKP Gold includes a <em>Content Server Configuration</em> page (under <em>Manage</em> > <em>System Administration Manager</em> > <em>System Settings</em>). However, it's not always necessary to explicitly configure a content server in order to enable learners to launch courses from servers other than the EKP server. Herewith, clarification of when it's necessary to explicitly configure a content server, and when it isn't.</p>
<img style="display: block; margin-left: auto; margin-right: auto;" src="http://3.bp.blogspot.com/_f7breNCi1sQ/SFVV6SB-wvI/AAAAAAAAAbc/Vc8n8rIFMAo/s400/Content+Server+Configuration-no+tear.png" border="0" alt="Content Server Configuration menu item" id="BLOGGER_PHOTO_ID_5212166603512136434" />
<p>EKP's content server configuration settings are designed to help you deliver courses to groups of learners whose connections to the main EKP server have limited bandwidth. You can upload the same course content to multiple content servers, and ensure that, when a learner launches a course, the course content will be delivered to the learner from a server that provides optimal delivery speeds. (The choice of server can be based on either the learner's identity or their network location.)</p>
<p>For example, suppose you need to deliver courses to employees located in your brand new lunar office. Since bandwidth between the moon and Earth is limited, you would like to ensure the course content is delivered to your lunar employees from a dedicated lunar content server, while Earth-based employees will continue to receive course content from your Earth-based server. You can use EKP's content server configuration to ensure that lunar employees receive course content from <code>moon.example.com</code>, while terrestrial employees receive the same content from <code>earth.example.com</code>.</p>
<p>However, there are other reasons besides bandwidth why you might want course content to reside on a different server from EKP. For example, the course content might require a server-side scripting language that is not available on the EKP server. Or the courses might be provided by a vendor that hosts the content themselves instead of providing it as files to be uploaded to an LMS server (which can be extremely convenient if the content is updated frequently). In these cases, although the content for a particular course will be delivered from a content server separate from the main EKP server, it will always be delivered from the <strong>same</strong> server regardless of the learner's identity and network location.</p>
<p><strong>If the content of any particular course should always be delivered from the same server</strong>, even if it is not the main EKP server, <strong>then it's not necessary to explicitly configure any content servers, and it's not necessary to be running EKP Gold.</strong> In this case, it's only necessary to ensure that the relevant launch URLs accurately reflect the location of the course content (which might require that they be specified as absolute, rather than relative, URLs), and this will work just fine on EKP Silver or EKP Bronze. Note that it's still possible to import the EKP course catalog entries from SCORM or AICC content packages even if the course content will not reside on the EKP server; however, in this case the packages will typically contain only course description files (i.e. <code>imsmanifest.xml</code> and related files in the case of SCORM-conformant courses, or AICC course structure files in the case of AICC-conformant courses), and not the actual course content files. Note also that you might still need to work around <a href="http://en.wikipedia.org/wiki/Cross-site_scripting">cross-site scripting</a> issues—however, it's generally more straightforward to do this by setting up a <a href="http://en.wikipedia.org/wiki/Reverse_proxy">reverse proxy</a> on the EKP server rather than by explicitly configuring content servers in EKP.</p>
<p>(To be really precise, we should state that content server configuration is required only if the host portion of any course content URLs needs to change based on the learner's identity or location—for example, <code>http://earth.example.com/path/to/content</code> versus <code>http://moon.example.com/path/to/content</code>. This is not quite the same as stating that the courses need to be launched from different servers, since it is possible to map a single host name to multiple physical servers—for example, the single host name <code>earth.example.com</code> might be mapped to multiple physical servers. However, this difference is unlikely to matter in practice unless special content distribution technology is being used.)</p>Robert Lowehttp://www.blogger.com/profile/16675118101660507438noreply@blogger.com0tag:blogger.com,1999:blog-3263211021568027449.post-81451323403193154022008-06-07T18:00:00.019+08:002008-06-17T04:44:59.500+08:00How can I change the size of the navigation frames for a course with multiple SCOs or AUs?<p>When you launch a <acronym title="Shareable Content Object Reference Model">SCORM</acronym> course with multiple shareable content objects (SCOs), an <abbr class="initialism" title="Aviation Industry CBT (Computer-Based Training) Committee">AICC</abbr> with multiple assignable units (AUs), or a course created using the Courseware Manager with multiple lessons, <abbr title="Enterprise Knowledge Platform" class="initialism">EKP</abbr> provides one or more frames containing navigation controls that the learners can use to navigate between the SCOs, AUs or lessons of the course.</p>
<p>If you are using EKP Gold or EKP Silver, <strong>you can change the size of these frames by creating a custom courseware template</strong>, provided that you have the appropriate administrative privileges. The steps are as follows.</p>
<ul>
<li>Create a courseware template package. You can use the files under <em>nd/fresco/template/course/default</em> as a guide. Copy the files to a new folder, edit <em>default.css</em> as required, then package the files as a zip file, ensuring that <em>default.css</em> is in the root of the package. (Note that you do <strong>not</strong> need to edit <em>default.css</em> to change the size of the navigation frames, but you can change the fonts and colors used for the navigation controls.)</li>
<li>Go to <em>Manage</em> > <em>Courseware Manager</em> > <em>Courseware Template Editor</em>, and click the <em>Create</em> button.<br /><img style="display: block; margin-left: auto; margin-right: auto;" src="http://1.bp.blogspot.com/_f7breNCi1sQ/SEppvjeWNKI/AAAAAAAAAbM/iclQZ5cENmQ/s400/1+Create.png" border="0" alt="Create button" id="BLOGGER_PHOTO_ID_5209092184704693410" /></li>
<li>In the new window, enter a name for the template, then click the <em>Browse...</em> button and select the courseware template package (zip file) you created.<br /><img style="display: block; margin-left: auto; margin-right: auto;" src="http://4.bp.blogspot.com/_f7breNCi1sQ/SEppi334MuI/AAAAAAAAAbE/E0MBcoYoThc/s400/2+Name+Zip+File.png" border="0" alt="Name and zip file selection fields" id="BLOGGER_PHOTO_ID_5209091966842188514" /></li>
<li>On the next page, click the top frame in the image.<br /><img style="display: block; margin-left: auto; margin-right: auto;" src="http://1.bp.blogspot.com/_f7breNCi1sQ/SEppXu9yrBI/AAAAAAAAAa8/yrJ_Uu2egxs/s400/3+Top+Frame.png" border="0" alt="Top frame image" id="BLOGGER_PHOTO_ID_5209091775472512018" /></li>
<li>In the <em>Top Frame</em> window, enter the desired height for the top frame. You can specify the height as a percentage of the total window height or as an absolute number of pixels.<br /><img style="display: block; margin-left: auto; margin-right: auto;" src="http://2.bp.blogspot.com/_f7breNCi1sQ/SEppLakkbzI/AAAAAAAAAa0/nPW3tilsvdA/s400/4+Height.png" border="0" alt="Height field" id="BLOGGER_PHOTO_ID_5209091563839582002" /></li>
<li>Set the width of the left frame by clicking the left frame in the image.<br /><img style="display: block; margin-left: auto; margin-right: auto;" src="http://1.bp.blogspot.com/_f7breNCi1sQ/SEpo4TRDnMI/AAAAAAAAAas/oOoM-dDI2sE/s400/5+Left+Frame.png" border="0" alt="Left frame image" id="BLOGGER_PHOTO_ID_5209091235461176514" /></li>
<li>Click <em>Update</em> to save the courseware template settings.</li>
</ul>
<p>You can configure a course with multiple SCOs, AUs or lessons to use your custom template by opening the course in the Catalog Editor, clicking <em>Navigation Setup</em> in the left frame, selecting your custom template from the <em>Template</em> drop-down list, then clicking the <em>Save</em> button in the top frame.</p>
<img style="display: block; margin-left: auto; margin-right: auto;" src="http://1.bp.blogspot.com/_f7breNCi1sQ/SEpoMwdp1NI/AAAAAAAAAak/hcG7kh-BZbg/s400/6+Template.png" border="0" alt="Courseware Template Selector" id="BLOGGER_PHOTO_ID_5209090487384397010" />
<p>Note that EKP needs to provide navigation controls for a course with multiple SCOs, AUs or lessons in order to ensure correct tracking, so you cannot remove the navigation frames. For a more detailed, see my <a href="http://www.robonekp.com/2007/02/can-i-create-custom-table-of-content.html" title="Can I create a custom Table of Contents for a multi-SCO SCORM course?">earlier post</a>.</p>Robert Lowehttp://www.blogger.com/profile/16675118101660507438noreply@blogger.com2tag:blogger.com,1999:blog-3263211021568027449.post-45345675980384245922008-05-26T20:56:00.005+08:002009-06-05T21:15:33.091+08:00Code samples: invoking APIs using C#<p>I've previously posted sample code that demonstrates how to invoke EKP's APIs using <a href="http://www.robonekp.com/2008/03/code-samples-invoking-apis-using-java.html">Java</a> and <a href="http://www.robonekp.com/2008/05/code-samples-invoking-apis-using-visual.html">Visual Basic</a>. Herewith, equivalent code using C#.</p>
<p>As with modern Visual Basic, C# is a .NET-based language. It's therefore fairly straightforward to translate our previous <a href="http://www.robonekp.com/2008/05/code-samples-invoking-apis-using-visual.html">Visual Basic sample code</a> into C#. As with the Visual Basic code, we will use an instance of <code>System.Net.WebClient</code> to make the API calls.</p>
<pre class="code">System.Net.WebClient client = new System.Net.WebClient();</pre>
<p>Again, we need to set the credentials that <code>WebClient</code> will use for the <a href="http://en.wikipedia.org/wiki/Basic_access_authentication" rel="nofollow">HTTP basic authentication</a> process. <strong>We must set the user name to a non-empty value</strong> to ensure that <code>WebClient</code> sends the authentication header, but the exact value doesn't matter since EKP ignores it. The password must match the value of the <code>authentication.key</code> property configured in the file <em>WEB-INF/conf/ekp.properties</em>.</p>
<pre class="code">String userName = "dummy";
String key = "mysecretkey";
client.Credentials = new System.Net.NetworkCredential(userName, key);</pre>
<p>To create a user account, we send an HTTP POST request to <em>/ekp/contentHandler/usersCsv</em>, including <a href="http://en.wikipedia.org/wiki/Comma-separated_values" rel="nofollow">CSV</a>-formatted data in the body of the request.</p>
<pre class="code">String data1 = "Action,UserID,Password,FamilyName,GivenName\r\n"
+ "A,joestudent,dummypass,Student,Joe\r\n";
client.UploadString("https://ekp.example.com/ekp/contentHandler/usersCsv",
data1);</pre>
<p>Similarly, we can enroll the user in a course with ID <em>Derivatives_101</em> by sending an HTTP POST request to <em>/ekp/enrollmentHandler</em>, again including appropriately-formatted CSV data in the body of the request.</p>
<pre class="code">String data2 = "USERID,LEARNINGID\r\n"
+ "joestudent,Derivatives_101\r\n";
client.UploadString("https://ekp.example.com/ekp/enrollmentHandler", data2);</pre>Robert Lowehttp://www.blogger.com/profile/16675118101660507438noreply@blogger.com0tag:blogger.com,1999:blog-3263211021568027449.post-27021198358042698992008-05-25T03:25:00.007+08:002009-06-05T21:15:33.091+08:00Code samples: invoking APIs using Visual Basic<p>I've previously posted <a href="http://www.robonekp.com/2008/03/code-samples-invoking-apis-using-java.html">sample Java code that demonstrates how to invoke EKP's APIs</a>. Herewith, equivalent code using Visual Basic .NET.</p>
<p>The .NET platform provides a couple of classes for sending <abbr class="initialism" title="Hypertext Transfer Protocol">HTTP</abbr> requests, specifically <code>System.Net.HttpWebRequest</code> and <code>System.Net.WebClient</code>. Either will do the job. However, <code>WebClient</code> is a little simpler to work with, so that's what we'll use.</p>
<pre class="code">Dim client As Net.WebClient = New Net.WebClient()</pre>
<p>API requests must include an <a href="http://en.wikipedia.org/wiki/Basic_access_authentication" rel="nofollow">HTTP basic authentication</a> header. Fortunately, both <code>WebClient</code> and <code>HttpWebRequest</code> have built-in support for HTTP basic authentication, so this is straightforward. The user name is ignored by EKP; however, <strong>we must set the user name to a non-empty value</strong>, otherwise <code>WebClient</code> won't send the authentication header. The password must match the value of the <code>authentication.key</code> property configured in the file <em>WEB-INF/conf/ekp.properties</em>.</p>
<pre class="code">Dim userName As String = "dummy" ' any non-empty value is okay
Dim password As String = "mysecretkey"
client.Credentials = New Net.NetworkCredential(userName, password)</pre>
<p>To create a user account, we send an HTTP POST request to <em>/ekp/contentHandler/usersCsv</em>, including <a href="http://en.wikipedia.org/wiki/Comma-separated_values" rel="nofollow">CSV</a>-formatted data in the body of the request.</p>
<pre class="code">Dim data1 As String _
= "Action,UserID,Password,FamilyName,GivenName" & ControlChars.CrLf _
& "A,joestudent,dummypass,Student,Joe" & ControlChars.CrLf
client.UploadString("https://ekp.example.com/ekp/contentHandler/usersCsv", _
data1)</pre>
<p>Similarly, we can enroll the user in a course with ID <em>Derivatives_101</em> by sending an HTTP POST request to <em>/ekp/enrollmentHandler</em>, again including appropriately-formatted CSV data in the body of the request.</p>
<pre class="code">Dim data2 As String = "USERID,LEARNINGID" & ControlChars.CrLf _
& "joestudent,Derivatives_101" & ControlChars.CrLf
client.UploadString("https://ekp.example.com/ekp/enrollmentHandler", data2)</pre>Robert Lowehttp://www.blogger.com/profile/16675118101660507438noreply@blogger.com0tag:blogger.com,1999:blog-3263211021568027449.post-73270611659378426912008-05-17T01:06:00.005+08:002008-06-07T15:21:44.726+08:00Which catalog fields can EKP extract from the metadata in a SCORM package?<p>When you import a <acronym title="Shareable Content Object Reference Model">SCORM</acronym> or <abbr class="initialism">IMS</abbr> content package, <abbr title="Enterprise Knowledge Platform" class="initialism">EKP</abbr> scans the file named <em>imsmanifest.xml</em> that every such package must contain, and extracts certain key information including: the course <strong>identifier</strong>; the <strong>title</strong> of the course; and information about the <strong>structure</strong> of the course including <acronym title="Uniform Resource Locator">URL</acronym>s and additional parameters used to launch the individual <acronym title="Shareable Content Object">SCO</acronym>s and assets.</p>
<p>In addition to this essential information, the package may also contain <em>metadata</em> in the form of a <strong>Learning Object Metadata (LOM) XML instance</strong> (either embedded in <em>imsmanifest.xml</em> itself, or else in a separate file referenced from <em>imsmanifest.xml</em>). If present, EKP can extract from this metadata any or all of the catalog fields listed below.</p>
<ul>
<li>Description</li>
<li>Vendor</li>
<li>Duration Comments</li>
<li>Objectives</li>
<li>Training Hours</li>
</ul>
<p>Shown below is a minimal LOM XML from which EKP can extract all of the additional catalog fields listed above. The relevant field values are shown in <strong>bold text</strong>. This example follows <a href="http://www.imsglobal.org/metadata/index.html" rel="nofollow">IMS LOM</a> as used in SCORM 1.2 packages; however, the same information could be extracted from an <a href="http://ltsc.ieee.org/wg12/" rel="nofollow">IEEE LOM</a> as used in SCORM 2004, and the differences would be minor.</p>
<p>(Note that this is a <em>minimal</em> XML instance from which EKP can extract the values mentioned above. The LOM XML schema denotes as mandatory some elements that are not actually required by EKP, hence this example is not actually valid against the schema, and the SCORM conformance test suite will report it as invalid.)</p>
<pre class="code"><lom xmlns="http://www.imsglobal.org/xsd/imsmd_rootv1p2p1">
<general>
<description>
<langstring><strong>Description</strong></langstring>
</description>
</general>
<lifecycle>
<contribute>
<centity>
<vcard>ORG:<strong>Vendor</strong></vcard>
</centity>
</contribute>
</lifecycle>
<educational>
<typicallearningtime>
<datetime><strong>02:30:00</strong></datetime>
<description>
<langstring><strong>Duration Comments</strong></langstring>
</description>
</typicallearningtime>
</educational>
<classification>
<purpose>
<value>
<langstring>Educational Objective</langstring>
</value>
</purpose>
<description>
<langstring><strong>Objective #1</strong></langstring>
</description>
</classification>
<classification>
<purpose>
<value>
<langstring>Educational Objective</langstring>
</value>
</purpose>
<description>
<langstring><strong>Objective #2</strong></langstring>
</description>
</classification>
<classification>
<purpose>
<value>
<langstring>Educational Objective</langstring>
</value>
</purpose>
<description>
<langstring><strong>Objective #3</strong></langstring>
</description>
</classification>
</lom></pre>Robert Lowehttp://www.blogger.com/profile/16675118101660507438noreply@blogger.com0tag:blogger.com,1999:blog-3263211021568027449.post-48173177460797511722008-03-25T00:32:00.005+08:002009-06-05T21:15:33.092+08:00Pragmatic API design<p>In a comment on a <a href="http://www.robonekp.com/2008/03/api-101.html">previous post</a>, Carfield Yim <a href="http://www.robonekp.com/2008/03/api-101.html#c3743115564684929796">suggests</a> an alternative style for API requests. We could dub this the <em>URL-encoded</em> style, since the <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17" rel="nofollow">content type</a> associated with the request format is <code><a href="http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1" rel="nofollow">application/x-www-form-urlencoded</a></code>. (Carfield's example involves updating a user's name, so the hypothetical service ought to require that the request be submitted using HTTP POST method rather than GET, since the action is <em><a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html" rel="nofollow">unsafe</a></em>.)</p>
<p>There is eternal debate in the software and web communities on the question of what APIs should look like, involving an acronym soup of terms such as <a href="http://en.wikipedia.org/wiki/SOAP" rel="nofollow">SOAP</a>, <a href="http://en.wikipedia.org/wiki/Representational_State_Transfer" rel="nofollow">REST</a>, <a href="http://en.wikipedia.org/wiki/Service-oriented_architecture" rel="nofollow">SOA</a>, <a href="http://en.wikipedia.org/wiki/Plain_Old_XML" rel="nofollow">POX</a> and <a href="http://en.wikipedia.org/wiki/Resource_oriented_architecture" rel="nofollow">ROA</a>. Without invoking too many buzzwords, we can observe that the following are both desirable properties of real-world APIs.</p>
<ol>
<li><strong>Support for invoking an operation, and viewing the response, directly in a browser.</strong> Typically the requests would need to be submitted using an HTML form. This can be very useful to a developer trying to make use of the API, since it becomes incredibly easy to test its behavior. Learning by experiment is often easier and more effective than reading a specification.</li>
<li><strong>Reuse of existing data formats.</strong> For example, one of the examples in the original post was concerned with creating a user account in EKP. The body of the request message contained <a href="http://en.wikipedia.org/wiki/Comma-separated_values" rel="nofollow">CSV</a> data in the format EKP expects for files uploaded using the User Data Loader. Hence, if an organization has already written code to generate data in that format (extracted from their HR system for example), it needs relatively little work to switch between either generating files for manual upload, or submitting the data directly using the API. On the other hand, the <code>application/x-www-form-urlencoded</code> content type isn't normally used outside of HTTP requests.</li>
</ol>
<p>(In passing, it's worth noting that SOAP does not satisfy either of these requirements particularly well. It fails completely with the first. As for the second, it's reasonably easy to reuse an existing format <em>if</em> it is XML-based <em>and</em> literal encoding is used rather than SOAP encoding. On the other hand, using a format that is not XML-based—because it predates XML for example—typically requires sending the data as some kind of SOAP attachment, which raises <a href="http://www.mail-archive.com/axis-user@xml.apache.org/msg08732/Fear_of_Attachments.pdf" rel="nofollow">a whole other set of issues</a>.)</p>
<p>In some cases, <strong>support for batch operations</strong> might also be a requirement. For example, if I need to update 1,000 user records, this will be considerably more efficient if the data can be sent in a single request. If 1,000 separate requests are required, then network latency will become a serious concern. (However, <a href="http://en.wikipedia.org/wiki/HTTP_pipelining" rel="nofollow">HTTP pipelining</a> could help if supported.)</p>
<p>As I see it, the URL-encoded request style meets the first requirement (it supports invocation from a browser), but fails to meet the second (it fails to reuse an existing data format for the request) and third (it is not well suited to batch operations). On the other hand, the CSV request style described in the original post fails to meet the first requirement, but meets the second and third. This seems like a reasonable trade-off in this case. However, the URL-encoded style seems like a reasonable approach in cases where support for large batch updates isn't a critical requirement, and there isn't an existing data format that obviously matches the desired content of the request message.</p>Robert Lowehttp://www.blogger.com/profile/16675118101660507438noreply@blogger.com1tag:blogger.com,1999:blog-3263211021568027449.post-30334679214458211082008-03-20T18:38:00.008+08:002009-06-05T21:15:33.092+08:00Code samples: invoking APIs using Java<p>Following on from my <a href="http://www.robonekp.com/2008/03/api-101.html">previous post</a>, I thought it might be useful to post some sample code that demonstrates how to invoke EKP's APIs, in this case using Java.</p>
<p>Every API request must include an <a href="http://en.wikipedia.org/wiki/Basic_access_authentication" rel="nofollow">HTTP basic authentication</a> header. If you are using Java's built-in <code><a href="http://java.sun.com/j2se/1.4.2/docs/api/java/net/HttpURLConnection.html" rel="nofollow">HttpURLConnection</a></code> class, you would need to construct and add this header manually. This is not hard, and the <a href="http://support.netdimensions.com/support/get.php?file=EN049_EKP_APIs_and_Web_Services_Overview.pdf&directory=whitepapers_47" rel="nofollow">EKP APIs and Web Services Overview</a> explains how to do it. However, it's even easier to use a library that supports HTTP basic authentication directly.</p>
<p>One such library is <a href="http://hc.apache.org/httpclient-3.x/index.html" rel="nofollow">Jakarta Commons <em>HttpClient</em></a>. <em>HttpClient</em> is free, open source, and distributed under version 2.0 of the Apache License, meaning that it can be used with minimal obligations in software that is not itself open source. (Note that <em>HttpClient</em> has dependencies on both <a href="http://commons.apache.org/logging/" rel="nofollow">Commons Logging</a> and <a href="http://commons.apache.org/codec/" rel="nofollow">Commons Codec</a>, so you will also need both of those to run this sample code.)</p>
<pre class="code">import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.StringRequestEntity;</pre>
<p>We begin by creating an instance of <code>HttpClient</code>, which is straightforward.</p>
<pre class="code">HttpClient httpClient = new HttpClient();</pre>
<p>We need to initialize the <code>HttpClient</code> object with the appropriate user name and password. The user name is ignored by EKP, so we simply use the empty string. The password must match the value of the <code>authentication.key</code> property configured in the file <em>WEB-INF/conf/ekp.properties</em>.</p>
<pre class="code">String userName = "";
String key = "mysecretkey";
httpClient.getParams().setAuthenticationPreemptive(true);
httpClient.getState().setCredentials(AuthScope.ANY,
new UsernamePasswordCredentials(userName, key));</pre>
<p>As described in the previous post, we can create a user account (here, <em>joestudent</em>) by sending an HTTP POST request to <em>/ekp/contentHandler/usersCsv</em>, and including <a href="http://en.wikipedia.org/wiki/Comma-separated_values" rel="nofollow">CSV</a>-formatted data in the body of the request.</p>
<pre class="code">PostMethod postMethod1 = new PostMethod(
"https://ekp.example.com/ekp/contentHandler/usersCsv");
String content1 = "Action,UserID,Password,FamilyName,GivenName\r\n"
+ "A,joestudent,dummypass,Student,Joe\r\n";
postMethod1.setRequestEntity(new StringRequestEntity(content1,
"text/plain", "UTF-8"));
httpClient.executeMethod(postMethod1);</pre>
<p>Similarly, we can enroll <em>joestudent</em> in a course with ID <em>Derivatives_101</em> by sending an HTTP POST request to <em>/ekp/enrollmentHandler</em>, again with appropriately-formatted CSV data in the body of the request.</p>
<pre class="code">PostMethod postMethod2 = new PostMethod(
"https://ekp.example.com/ekp/enrollmentHandler");
String content2 = "USERID,LEARNINGID\r\n"
+ "joestudent,Derivatives_101\r\n";
postMethod2.setRequestEntity(new StringRequestEntity(content2,
"text/plain", "UTF-8"));
httpClient.executeMethod(postMethod2);</pre>Robert Lowehttp://www.blogger.com/profile/16675118101660507438noreply@blogger.com0tag:blogger.com,1999:blog-3263211021568027449.post-31688024349771490382008-03-20T14:44:00.008+08:002009-06-05T21:15:33.092+08:00API 101<p>We just finished a fairly intensive training session here in Hong Kong with a couple of partners. The subject was ostensibly our customization toolkit, but we also spent quite a bit of time discussing APIs and how EKP can be integrated with existing web sites, portals and applications. Every time we conduct these training sessions, this topic expands.</p>
<p>Here's an outline of what we covered, with links to the relevant documentation.</p>
<ol>
<li>The first step in seamlessly combining EKP functionality with an existing site is understanding <a href="http://www.robonekp.com/2007/06/can-i-use-external-authentication.html">how to implement web-based <strong>single sign-on</strong></a>.</li>
<li>With a single sign-on mechanism in place, it's possible to have external pages <strong>link directly to specific pages in EKP</strong>, and, in particular, to <a href="http://www.robonekp.com/2007/07/can-i-launch-courses-directly-from.html"><strong>launch courses directly from external pages</strong></a>. (See also: <a href="http://www.robonekp.com/2007/02/how-can-i-link-directly-to-page-in-ekp.html">How can I link directly to a page in EKP without losing the navigation frames?</a>)</li>
<li>Data can be manipulated in, or extracted from, EKP using the APIs described in the <a href="http://support.netdimensions.com/support/get.php?file=EN049_EKP_APIs_and_Web_Services_Overview.pdf&directory=whitepapers_47" rel="nofollow">EKP APIs and Web Services Overview</a>. Examples:
<ul>
<li>To ensure that EKP has a user account for a specific user ID (as part of the single sign-on process for example), have the external site send a direct server-to-server HTTP POST request to <em>/ekp/contentHandler/usersCsv</em>, with <a href="http://en.wikipedia.org/wiki/Comma-separated_values">CSV</a>-formatted data (following the User Data Loader format) in the body of the request. Note that most of the columns are optional when the User Data Loader format is used, so the following is valid for example:<pre class="code">Action,UserID,Password,FamilyName,GivenName
A,joestudent,mypassword,Student,Joe</pre></li>
<li>To ensure that a specific user is enrolled in a specific course (prior to launching the course for example), have the external site send a direct server-to-server HTTP POST request to <em>/ekp/enrollmentHandler</em> with CSV-formatted data in the body of the request. For example:<pre class="code">USERID,LEARNINGID
joestudent,Derivatives_101</pre></li>
<li>To retrieve a complete training history, in XML format, for user <em>joestudent</em> (to display within an external page for example), have the external site send a direct server-to-server HTTP GET request to <em>/ekp/contentGenerator/trainingHistoryXml?userId=joestudent</em>.
</ul>
</li>
</ol>
<p><strong>Update:</strong> I posted <a href="http://www.robonekp.com/2008/03/code-samples-invoking-apis-using-java.html">Java code samples for creating a user account and enrolling a user in a course</a>.</p>
<p><strong>Update 2:</strong> <a href="http://www.robonekp.com/2008/03/pragmatic-api-design.html">Musings on the invocation style of the API operations discussed above</a>, prompted by <a href="http://www.robonekp.com/2008/03/api-101.html#c3743115564684929796">Carfield's comment</a>.</p>Robert Lowehttp://www.blogger.com/profile/16675118101660507438noreply@blogger.com2tag:blogger.com,1999:blog-3263211021568027449.post-55655114373923513932008-03-19T02:48:00.003+08:002008-06-07T15:21:44.726+08:00NoSuchMethodError when upgrading to EKP 5.0<p>We've had a few reports of the following problem: when connecting to an EKP site that has been upgraded to EKP 5.0, a "Cannot connect to database" page is seen, and a message like the one below appears in the log file (<em>WEB-INF/logs/ekp.log</em>).</p>
<pre class="code">java.lang.NoSuchMethodError: javax.xml.parsers.DocumentBuilderFactory.getSchema()Ljavax/xml/validation/Schema;
at com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.<init>(DocumentBuilderImpl.java:110)
at com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl.newDocumentBuilder(DocumentBuilderFactoryImpl.java:101)
at com.sun.msv.verifier.jaxp.DocumentBuilderFactoryImpl.newDocumentBuilder(DocumentBuilderFactoryImpl.java:145)</pre>
<p>This appears to happen if EKP is running on an older version of Tomcat 4.1. Older versions of Tomcat 4.1 will override the XML parser libraries that are included in the Java runtime, and these libraries conflict with the versions included in J2SE 5.0, which EKP 5.0 requires.</p>
<p>The simplest solution is to delete the parser libraries that are included with Tomcat 4.1. These can be found in the <em>common/endorsed/</em> directory under the Tomcat home directory. These libraries are not required when running under J2SE 5.0.</p>Robert Lowehttp://www.blogger.com/profile/16675118101660507438noreply@blogger.com6tag:blogger.com,1999:blog-3263211021568027449.post-61947591998708270812008-03-19T02:28:00.006+08:002008-04-02T22:46:24.280+08:00On Safari<p>I just sat down at my computer and was greeted with this.</p>
<a href="http://1.bp.blogspot.com/_f7breNCi1sQ/R-AKhvdcTeI/AAAAAAAAAW0/O-HOxDKOOew/s1600-h/Apple+Software+Update.png"><img style="cursor:pointer; cursor:hand;" src="http://1.bp.blogspot.com/_f7breNCi1sQ/R-AKhvdcTeI/AAAAAAAAAW0/O-HOxDKOOew/s400/Apple+Software+Update.png" border="0" alt="Apple Software Update window with Safari 3.1" id="BLOGGER_PHOTO_ID_5179151146267987426" /></a>
<p>I have never installed Apple's Safari browser, but I do have iTunes and QuickTime. Apparently Apple is trying to leverage iTunes and QuickTime to push Safari to as many PCs as possible.</p>
<p>If this is at all successful, Safari's usage share could increase significantly.</p>Robert Lowehttp://www.blogger.com/profile/16675118101660507438noreply@blogger.com0tag:blogger.com,1999:blog-3263211021568027449.post-83755467011677720992008-03-18T17:31:00.009+08:002011-05-26T21:43:08.708+08:00Custom login pages<p>Most EKP sites will use a custom login page rather than the standard page that ships with EKP. This is done by creating a file named <code>login.wm</code> and placing it in the <code>WEB-INF/conf/</code> directory. The contents of this page are essentially HTML, with some placeholders for dynamic content that use the <a href="http://velocity.apache.org/engine/devel/vtl-reference-guide.html" rel="nofollow">Velocity Template Language (VTL)</a>.</p>
<p>The standard login page, which can be used as a guide, can be extracted from the file <code>WEB-INF/lib/ekp.jar</code> using a zip utility such as <a href="http://www.winzip.com/index.htm" rel="nofollow">WinZip</a> or <a href="http://www.rarlab.com/" rel="nofollow">WinRAR</a>. The location of the page within the <code>ekp.jar</code> file is <code>com/netdimen/tx/auth/login/login.wm</code> for EKP 4.7 and above, or <code>com/netdimen/tx/login/login.wm</code> for EKP 4.6.</p>
<p>One important point that is easy to overlook is that the page should contain the code shown below immediately after the opening <code><form></code> tag of the login form.</p>
<pre class="code"> #if ($target != "")
<input type="hidden" name="target" value="$target" />
#end</pre>
<p>This code will ensure that, if a user who is not logged in attempts to access a page that requires authentication (which would happen if the user's session had timed out, for example), EKP will "remember" which page the user was trying to access and forward the user to that page after prompting her to log in. If users are always forwarded to the default start page after logging in, even if they were prompted to log in while trying to access another page, it is most likely because the above code is missing from the login page template.</p>
<p>(Note that the default static <code>index.html</code> page simply redirects the user to the dynamic login page described above. We recommend leaving the <code>index.html</code> page as-is and simply creating a <code>login.wm</code> page as described above, to avoid duplicating the custom login page HTML code.)</p>
<p>If your EKP site is hosted by NetDimensions, these steps will normally be performed for you by our staff.</p>Robert Lowehttp://www.blogger.com/profile/16675118101660507438noreply@blogger.com2