Posts Tagged With: Russ Maher

Finding user roles in #XPages

I’ve written a piece before on roles in XPages, but since that dealt with using the ACL to limit access to a page and not about the programmatic use of roles, I wanted to return to the issue.

Back in old Notes, if we wanted to hide something based if the user did not have a certain role in the ACL, we could use a pretty simple formula:

!@IsMember(“[roleNameHere]”; @UserRoles)

Remarkably, it’s not that much harder in XPages, but there are some important wrinkles to be concerned about. As noted previously and by Russ Maher on his blog, you must remember to use your brackets [] and also keep in mind the result you want (true or false). Remember that the XPages formulas are ‘rendered’ formulas, meaning you want to ‘true’ to display the result and ‘false’ to hide it, so you’d use:

@IsMember(“[roleNameHere]”, context.getUser().getRoles());

Here’s the more complete source code for the rendered formula:

<xp:this.rendered><![CDATA[${javascript:@IsMember("[Testing]", context.getUser().getRoles());}]]></xp:this.rendered>

Now, I’m not sure what impact of referencing the user roles that way has on performance, but since I now I’m using them in many rendered formulas all over my application, I decided to compute it once and then reuse it many times. I put a few extra lines into a control that’s on my main application layout control to drop it into a sessionscope variable. I suppose I might shave a millisecond off if I only computed that once per session, but I didn’t go that far.

<xp:this.beforePageLoad>
<![CDATA[${javascript:var roles = context.getUser().getRoles();
sessionScope.userRoles = roles;}]]>
</xp:this.beforePageLoad>

Then, in order to check to see if my user has one of three roles when rendering an item, I could use this code:

<xp:this.rendered><![CDATA[${javascript:var manager = @IsMember("[InventoryMgr]", sessionScope.userRoles);
var viewer = @IsMember("[InventoryViewer]", sessionScope.userRoles);
var grantsManager = @IsMember("[InvMgrGrants]", sessionScope.userRoles);
if ( manager || viewer || grantsManager ) { return true };
return false; }]]>
</xp:this.rendered>

Now, I also found that sometimes I need to determine the user’s role in my Java code. That’s also not that hard, except that vectors are not quite arrays. If there is a single value, it’s not the same as a multiple value vector. I’m not sure if this is a Notes implementation issue or if it’s the way Java always handles vectors. That is, if it’s a single value, it puts our brackets [] around the value, but it does NOT for multiple values. So, when I was using the code written for us, it wasn’t always picking up the roles correctly. Once I simply told it to check both ways, our code worked more cleanly. (The reference to ExtLibUtil comes from the original code, so I didn’t modify it.)

public Vector getCurUserRoles() {
	try {
		curUserRoles = ExtLibUtil.getCurrentDatabase().queryAccessRoles(ExtLibUtil.getCurrentSession().getEffectiveUserName());
	} catch (NotesException e) {
		this.debug("getCurUserRoles ERROR: " + e.getMessage(), "error");
		curUserRoles = new Vector();
	}
	return curUserRoles;
}

public boolean hasRole(String role, String uname) {
	try {
		Vector roles = this.getCurUserRoles();
		if (roles.contains(role))
			return true;
		if (roles.contains("["+role+"]"))
			return true;
		return false;
	} catch (Exception e) {
		this.debug("hasRole ERROR: " + e.getMessage(), "error");
		return false;
	}
 }

Note that the debugging uses Mark Leusink‘s DebugToolbar, which I highly recommend to everyone.

Categories: Java, Old Notes, Security, Xpages | Tags: , , , , , , | 2 Comments

Exporting from #XPages to Excel without Excel, Part 2

Yesterday, I posted about exporting to Excel without having Excel on the destination device, which I found particularly useful when using my Android tablet and the Chrome browser. It was a relatively long post, necessitating a two-part post.

Today, I want to spend some time showing the server-side javascript library that I’ve developed (based on the one Russ Maher presented at the AdminDev 2012 conference). So, we’re going to delve into some Apache POI, examining how we pass the configuration information in and which lines of code implement that. I expect this examination will be useful for me as well, since it will open my eyes wider to the potential within the code. Russ assures me that while he dabbles in POI, Bill Buchan actually speaks POI, so there is at least one Notes genius who knows far more than both of us….

The Function Call

Before we delve into the code, lets first examine what we’re passing to the function. Each of these values is acquired either from the XPage or from the report configuration. Using these, we have a lot of flexibility to create and format our spreadsheet.

function createWorkbookStreamWithLabels(workbookName,sheetName,fieldList,viewName,colLabels,totalLabels)

workbookName: From the XPage, which defaults the value to the report name selected. It will be used as the filename for our spreadsheet.

sheetName: Also from the XPage, defaulting to ‘Report’, but editable by the user to place as the worksheet name for the single worksheet of our spreadsheet.

fieldList: From the report configuration. This is a list of the field names, in order, to be used as values in the columns. This implementation uses field values instead of column values, but it could easily be modified to use those instead.

viewName: From the report configuration. View containing the documents to be exported.

colLabels: From the report configuration. These will end up in the first row of the spreadsheet and be styled for emphasis.

totalLabels: From the report configuration. These are used to identify which columns will get totaled. This was clearer and more reliable than using column numbers.

Creating the Stream

I actually want to start by examining the last piece of code in the library, the download of the file to the client. To me, the great strength of this whole undertaking is that I can get a spreadsheet to my device without any Excel software being loaded there. As I noted yesterday, when I used the XPage and this script library to create a spreadsheet on my tablet, the usefulness skyrocketed in the estimation of my office-mate, Ariwan. “You could download the inventory to a tablet, walk around, making notes on your tablet and then return and update the database.” Of course, I started thinking one step further and wanting to import the marked up spreadsheet right back into the database, but that code will have to wait for a while. Nonetheless, we see an immediate impact beyond just creating spreadsheets.

By using the OutputStream, we’re able to take the export that we build in the workbook, wb, and simply prompt the user to download the file as an output stream. On my tablet, this downloaded the file without prompting, which was exactly what I wanted. In a quick test, I tried to use .xlsx, but it refused to work – that might be fixed in the later releases of the POI library (I’m using 3.6 currently, though 3.9 has already been released).

//Create the filename for the spreadsheet
var fileName = workbookName+".xls";

// The Faces Context global object provides access to the servlet environment via the external content
var extCont = facesContext.getExternalContext();
// The servlet's response object provides control to the response object
var pageResponse = extCont.getResponse();
//Get the output stream to stream binary data
var pageOutput = pageResponse.getOutputStream();

// Set the content type and headers
pageResponse.setContentType("application/x-ms-excel");
pageResponse.setHeader("Cache-Control", "no-cache");
pageResponse.setHeader("Content-Disposition","inline; filename=" + fileName);
//Write the output, flush the buffer and close the stream
wb.write(pageOutput);
pageOutput.flush();
pageOutput.close();

Error-handling

I found myself trying to trouble-shoot and, as always, being frustrated with XPages because I don’t know how to do that very well. I got real used to being able to step through my LotusScript, to being able to look at field values on a document using properties, to using print statements and popups to track what was going on, but I haven’t yet filled my toolkit with ways to trouble-shoot XPages. So, I borrowed Don Mottolo’s postValidationError function from his SSJS Form Validation XSnippet. Whenever I want to return an error, I just pass the control and some text to that function and it should post the message in my display errors control. I’ve only got a few of these in the library because it started working properly before I added all the potential error-trapping and who has the patience to add all that? (Yeah, I’ll add more later, since I’m sure I’ll run into bugs in production. Idiot-proof code is impossible because idiots are just too darn inventive!)

function postValidationError(control, msg) {
if ((typeof msg) != "string")
	return;
var msgObj = new javax.faces.application.FacesMessage(javax.faces.application.FacesMessage.SEVERITY_ERROR, msg, msg);
facesContext.addMessage(control.getClientId(facesContext), msgObj);
control.setValid(false);
}

Create some cell styles

As I mentioned yesterday, we deal with a lot of international date formats and, in any reporting, dates are all over the place. So, I added a date format to be applied to those cells. Similarly, as mentioned above, I want my headers to get some styling to emphasize them.

I’ve also designated my numeric format, using two digits after the decimal. Note that if the user has their machine set to use European number formatting, with periods separating thousands and a comma to show the decimal, the export won’t care – it passes the values and the user’s local settings display it according to their preferences. I’m not certain if the preferences at the server (if any) would have any effect on how you must code it, so am curious what experience others have there.

I’m sure that if we delve into the POI documentation, we can learn all kinds of ways to format the cells and I expect that once this deploys, we’ll be working on learning those. We start, however, with just these three.

//Create helper class and styles for dates
var createHelper:HSSFCreationHelper = wb.getCreationHelper();
var dateStyle:HSSFCellStyle = wb.createCellStyle();
dateStyle.setDataFormat(HSSFDataFormat.getBuiltinFormat("m/d/yy"));

var headerStyle:HSSFCellStyle = wb.createCellStyle();
var headerFont:HSSFFont = wb.createFont();
headerFont.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD);
headerStyle.setFont(headerFont);

// set number formatting to use a thousands separator and two digits
// note that it will display using the user's thousands separator and decimal notation
// rather than forcing US-standard settings
var format:DataFormat = wb.createDataFormat();
style.setDataFormat(format.getFormat("#,##0.00"));

Handle various data types

The next important issue is that when passing the data through, you must make sure the Excel file understands the data type.

//depending on the type of value, use proper method to retrieve
if (valueType.endsWith("DateTime")) {
	var reportValue = (String) (itemValue);
} else if (valueType.endsWith("number")) {
	var reportValue = Number (itemValue);
} else if (valueType.endsWith("string")) {
	var reportValue = itemValue;
} else if (valueType.endsWith("Vector")) {
	var reportValue = (String) (itemValue); // multi-value field
	reportValue = reportValue.replace("[",""); // remove opening bracket
	reportValue = reportValue.replace("]",""); // remove closing bracket
} else {
	var reportValue = itemValue.toString;
}
if ( reportValue == null ) {
	reportValue = "Value not found"
}

The Whole Library

Everything else in here should be pretty self-explanatory. We simply walk through the view, almost using LotusScript, moving from document to document, getting the appropriate field values and passing them to the Excel file. When it works, it’s lovely.

function createWorkbookStreamWithLabels(workbookName,sheetName,fieldList,viewName,colLabels,totalLabels){
//import the appropriate java packages
importPackage(java.lang);
importPackage(org.apache.poi.hssf.usermodel);
importPackage(org.apache.poi.hssf.util);

var control = getComponent("comboBox1");

//Find the database connection document
var dbConView:NotesView = database.getView("TSDbConnectionLU");
var doc:NotesDocument = dbConView.getDocumentByKey("TAMIS II main db");
//Get the maindb object
if (doc != null){
	var maindb = session.getDatabase(doc.getItemValueString("DbServer"),doc.getItemValueString("DbPath"), false);
}
if ( maindb == null ) {
	postValidationError(control,"Main db Not Found");
}
var view:NotesView=maindb.getView(viewName);
if ( view == null ) {
	postValidationError(control,"View Not Found");
}

//Create placeholders for the notes document and temperary document
var doc:NotesDocument;
var ndoc:NotesDocument;

//Create a new workbook object from the poi library
var wb:HSSFWorkbook = new HSSFWorkbook();
//Create additional sheets using same sytnax and different sheet name
var sheet1:HSSFSheet = wb.createSheet(sheetName);

//Create helper class and styles for dates
var createHelper:HSSFCreationHelper = wb.getCreationHelper();
var dateStyle:HSSFCellStyle = wb.createCellStyle();
dateStyle.setDataFormat(HSSFDataFormat.getBuiltinFormat("m/d/yy"));

var headerStyle:HSSFCellStyle = wb.createCellStyle();
var headerFont:HSSFFont = wb.createFont();
headerFont.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD);
headerStyle.setFont(headerFont);

//Create the Column Header Rows
var row:HSSFRow = sheet1.createRow(0);
for( i = 0 ; i <= colLabels.length-1 ; i++){
	var hCell:HSSFCell = row.createCell((java.lang.Integer)(i));
	hCell.setCellValue(colLabels[i]);
	hCell.setCellStyle(headerStyle);
}

// Style the cell with borders all around. GREY 25% - 22, GREY 40% - 55, GREY 50% - 23, GREY 80% - 63
var style:HSSFCellStyle= wb.createCellStyle();
style.setBorderBottom(HSSFCellStyle.BORDER_THIN);
style.setBottomBorderColor(55);
style.setBorderLeft(HSSFCellStyle.BORDER_THIN);
style.setLeftBorderColor(55);
style.setBorderRight(HSSFCellStyle.BORDER_THIN);
style.setRightBorderColor(55);
style.setBorderTop(HSSFCellStyle.BORDER_THIN);
style.setTopBorderColor(55);

// set number formatting to use a thousands separator and two digits
// note that it will display using the user's thousands separator and decimal notation
// rather than forcing US-standard settings
var format:DataFormat = wb.createDataFormat();
style.setDataFormat(format.getFormat("#,##0.00"));

//initialize the row and cell counters
var rowCount=0;
var cellCount=0;

//Get the first document in the view and then cycle through
//the view to create the detail rows.
doc = view.getFirstDocument();

while(doc!=null){
var row:HSSFRow = sheet1.createRow(++rowCount);
for( f = 0 ; f <= fieldList.length-1 ; f++){
	var itemValue:NotesItem = doc.getColumnValues().elementAt(Number (fieldList[f]) - 1 );
	var valueType = typeof(itemValue);

	if (itemValue != null) {
		var reportValue;
		//depending on the type of value, use proper method to retrieve
		if (valueType.endsWith("DateTime")) {
			var reportValue = (String) (itemValue);
		} else if (valueType.endsWith("number")) {
			var reportValue = Number (itemValue);
		} else if (valueType.endsWith("string")) {
			var reportValue = itemValue;
		} else if (valueType.endsWith("Vector")) {
			var reportValue = (String) (itemValue); // multi-value field
			reportValue = reportValue.replace("[",""); // remove opening bracket
			reportValue = reportValue.replace("]",""); // remove closing bracket
		} else {
			var reportValue = itemValue.toString;
		}
		if ( reportValue == null ) {
			reportValue = "Value not found"
		}
		var dataCell:HSSFCell = row.createCell((java.lang.Integer)(f));
		dataCell.setCellValue(reportValue);
		dataCell.setCellStyle(style);
	}
}
doc = view.getNextDocument(doc);
}

// add a row for totaling and put the formula in where it is useful
var row:HSSFRow = sheet1.createRow(++rowCount);

// cycle through totalLabels, find match in colLabels, make a totalling cell
for( i = 0 ; i <= totalLabels.length-1 ; i++){
for( j = 0 ; j <= colLabels.length-1 ; j++){
if ( totalLabels[i] == colLabels[j] ) {
var summaryCell:HSSFCell = row.createCell((java.lang.Integer)(j));
// add the SUM formula to that cell
summaryCell.setCellFormula("SUM(INDIRECT(CONCATENATE(ADDRESS(2,COLUMN()),\":\")&ADDRESS("+rowCount+",COLUMN())))");
}
}
}

// auto size all the columns
for( j = 0 ; j <= colLabels.length-1 ; j++){
sheet1.autoSizeColumn (j);
}

//Create the filename for the spreadsheet
var fileName = workbookName+".xls";

// The Faces Context global object provides access to the servlet environment via the external content
var extCont = facesContext.getExternalContext();
// The servlet's response object provides control to the response object
var pageResponse = extCont.getResponse();
//Get the output stream to stream binary data
var pageOutput = pageResponse.getOutputStream();

// Set the content type and headers
pageResponse.setContentType("application/x-ms-excel");
pageResponse.setHeader("Cache-Control", "no-cache");
pageResponse.setHeader("Content-Disposition","inline; filename=" + fileName);
//Write the output, flush the buffer and close the stream
wb.write(pageOutput);
pageOutput.flush();
pageOutput.close();

//  Terminate the request processing lifecycle.
facesContext.responseComplete();
}
function postValidationError(control, msg) {
if ((typeof msg) != "string")
return;
var msgObj = new javax.faces.application.FacesMessage(javax.faces.application.FacesMessage.SEVERITY_ERROR, msg, msg);
facesContext.addMessage(control.getClientId(facesContext), msgObj);
control.setValid(false);
}

The sample database is available, as is the first post on this topic.

Categories: Server-Side Javascript, Xpages | Tags: , , , , , , , , , , , | 15 Comments

Exporting from #XPages to Excel without Excel, Part 1

As much as I’d love my users to simply access their data in my Notes databases, I know they’re going to need to export it. Mostly, they want to be able to manipulate it to analyze it, but they also want to create static reports and to send those reports to external parties that we don’t want accessing our data live. In old Notes, I could force train people to use the export functions of Notes, or with the advent of Copy As Table, scream at teach them to cut-and-paste. Back when I was at a government agency, helping them email their order via spreadsheet from an outside vendor in a semi-automated process, it required the user to have Excel on their machine (initially, only specific versions of Excel, but I learned to get beyond that). Fortunately, Russ Maher taught me that you don’t have to do it that way in his talk Extending Your XPages Applications with Java at AdminDev 2012.

My favorite part of his talk, since I’d done the aforementioned Notes-to-Excel export, was when he talked about creating Excel files even if the user didn’t have Excel. Apache POI is a Java API for Microsoft documents (download page for the JAR files) and by loading the JAR file into your Notes databases, you can use all of the functionality in your XPages applications. Thus, I can use my Motorola XOOM tablet to access the project database, select the inventory report, generate the Excel file and download it directly to my tablet without having Excel on the tablet. I showed the result to office mate and he came up with the inventory idea — no need to print out the inventory and you can make notes on your tablet as you walk around. Heck, I’m sure I could also write something that would process changes back into the database there as well, but that remains for a future date.

Architectural Information

Before we plunge into the code, let me first explain the architecture and use case involved here. Each of our projects around the world uses one of our applications, which we call TAMIS. In the past, this meant one Notes database for each project,  with perhaps 70 projects active at a time. Each database would start with the same design and then be customized for the project. This has usually meant design changes rather than just configuration, but both methods are used. So, there will be some configuration that is done by local staff and some by the development team. Configuration of these Excel reports is something that has always been done by the development team.

In the XPages version our application, there are actually four databases: Shared Resources, Main, Attachments and Workflow. The design work was done by Scott Good’s folks at Teamwork Solutions, so it bears many marks of their Process It! workflow engine. Shared Resources contains all the XPage design elements, configuration documents and some general information. Main is where the data goes. Attachments is obvious, but Workflow not as much – it contains both the workflow configuration and the workflow tracking documents.

Given all of that, we will have our code in Shared Resources and our data over in Main. Thus, we want to launch from one database, grab data from another and return it all to the user as a seamless download that pops into his machine just by clicking one button on our XPage.

So, how do we do this?

POI JarAdd JAR to database

Well, first thing to do is to put the JAR file into your Notes database. I’m still on 8.5.3, so I have to put it in \WEB-INF\lib, while you can bring it in directly as a design element in Notes 9. As you can see in the image here, I’m using version 3.6 of the JAR file, despite the fact that 3.9 is the current release, but I don’t really mind being behind the times a little bit, as long as it works. It looks like 3.7 and 3.8 added some fixes for handling dates and numbers better, so I will probably upgrade after our next pilot rolls out. We have seen occasional issues with dates, being a very international company.

ReportKeywordCreate the Configuration Form

The was arguably my favorite part of the process. Well, until it actually started spitting out spreadsheets. Why did I enjoy it so much? Is it because it’s ‘old Notes’? I don’t think so. While the form did build off the keyword documents that my old development mentors (Elvis Lezcano and John Mirza) created back in the 1990s when we were all at Exxon-Mobil, the neat part was utilizing some of the knowledge I’d gained about navigating design elements in simple, front-end LotusScript. Basically, the user selects the view (via name or alias) and then can choose the columns right from the view design rather than requiring any pre-configuration by me. While I do need to work on the configuration form to add more choices about sorting and styling, I got pretty happy with the results.

The configuration form supplies the view name, the column headers and numbers, and identifies which columns to total. This gets used by the Export Stream Library when the user initiates the export.

Create your Export XPage

This was the simplest design element, though also, since I’m still so new to XPages, the most challenging. With the configuration document, I was working with LotusScript, so the new wrinkles were just fun extensions of my knowledge. The Export Stream Library was initially just something I took wholesale from Russ’ presentation. While I did start with Russ’ XPage, I ended up, because of the configuration document and the UI things I wanted to do, making myself jump through a few hurdles. Worse yet, when I went to move it from it’s original implementation, I stumbled several times on the fact that the button was…. disabled. Nothing like clicking and checking your code 10 different ways and then examining it using ‘Inspect Element’ to realize the problem is that the disabled attribute simply computes as true every time.

Our Export Xpage allows the user to select from all of the report configurations we’ve created and provide both the filename (to which it will append .xls) and the name for the worksheet. I’m sure that it wouldn’t take much additional work to export multiple worksheets into a single file for a more complex and useful product, but we’re starting pretty simple.

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core"
	xmlns:xp_1="http://www.ibm.com/xsp/coreex">
	<xp:this.resources>
		<xp:script src="/CreateExcelWorkbookStream.jss" clientSide="false">
		</xp:script>
	</xp:this.resources>
	<xp:label id="label1" value="Create Reports"></xp:label>
	<xp:br></xp:br>
	<xp:br></xp:br>
	<xp:table>
		<xp:tr>
			<xp:td>
				<xp:label value="Choose Report:" id="label2"></xp:label>
			</xp:td>
			<xp:td>
				<xp:comboBox id="comboBox1">
					<xp:selectItems id="selectItems1">
						<xp:this.value><![CDATA[#{javascript:var noval = [];
						noval[0] = "Please select a report";
						var forms = @DbColumn(@DbName(),"ExcelReports",1);
						var vals = noval.concat(forms);
						return vals;}]]></xp:this.value>
					</xp:selectItems>
					<xp:eventHandler event="onchange" submit="true"
						refreshMode="complete" refreshId="wbName" id="eventHandler1">
						<xp:this.script><![CDATA[var x= '#{javascript:getClientId("comboBox1")}'; var z= '#{javascript:getClientId("wbName")}'; var tmp = document.getElementById(x).value; document.getElementById(z).value=tmp.replace(/ /g,"_"); ]]></xp:this.script>
					</xp:eventHandler>
				</xp:comboBox>
			</xp:td>
		</xp:tr>
		<xp:tr>
			<xp:td>
				<xp:label value="File Name:" id="label4" for="wbName">
				</xp:label>
			</xp:td>
			<xp:td>
				<xp:inputText id="wbName"
					disableClientSideValidation="true" required="true"
					defaultValue="Sample">
					<xp:this.validators>
						<xp:validateRequired>
							<xp:this.message><![CDATA[#{javascript:return getLabelFor(this).getValue() + " is a required field.";}]]></xp:this.message>
						</xp:validateRequired>
					</xp:this.validators>
				</xp:inputText>
			</xp:td>
		</xp:tr>
		<xp:tr>
			<xp:td>
				<xp:label value="Sheet Name:" id="label5"
					for="sheetName">
				</xp:label>
			</xp:td>
			<xp:td>
				<xp:inputText id="sheetName"
					disableClientSideValidation="true" required="true"
					defaultValue="Report">
					<xp:this.validators>
						<xp:validateRequired>
							<xp:this.message><![CDATA[#{javascript:return getLabelFor(this).getValue() + " is a required field.";}]]></xp:this.message>
						</xp:validateRequired>
					</xp:this.validators>
				</xp:inputText>
			</xp:td>
		</xp:tr>
		<xp:tr>
			<xp:td>

			</xp:td>
			<xp:td>
				<xp:messages id="messages1"></xp:messages>
			</xp:td>
		</xp:tr>
	</xp:table>
	<xp:br></xp:br>
	<xp:br></xp:br>
	<xp:button value="Create Report" id="button1">
		<xp:this.disabled><![CDATA[#{javascript:getComponent("comboBox1").getValue() == "Please select a report";}]]></xp:this.disabled>
		<xp:eventHandler event="onclick" submit="true"
			refreshMode="complete">
			<xp:this.action><![CDATA[#{javascript:
function postValidationError(control, msg) {
    if ((typeof msg) != "string")
            return;
    var msgObj = new javax.faces.application.FacesMessage(javax.faces.application.FacesMessage.SEVERITY_ERROR, msg, msg);
    facesContext.addMessage(control.getClientId(facesContext), msgObj);
    control.setValid(false);
}
var control = getComponent("comboBox1");

var wbName = getComponent("wbName").getValue();
var sheetName = getComponent("sheetName").getValue();
var formName = getComponent("comboBox1").getValue();
var fpapp:NotesDatabase = session.getDatabase(@DbName()[0],@DbName()[1]);
var lkey:java.util.Vector = new java.util.Vector;
lkey.addElement(formName);
var lview = fpapp.getView("ExcelReports");
var doc:NotesDocument = lview.getDocumentByKey(lkey,true);
if ( !@IsNull (doc) ) {
	var viewName = doc.getItemValue("ViewName");
	postValidationError(control,"ViewName: " + viewName);
	var columns = doc.getItemValue("columnNumbers");
	postValidationError(control,"columnNumbers: " + columns);
	var colFields = [];
	for(var i=0;i<=columns.length-1;i++){
		colFields.push(columns[i]);
	}
	labelList = doc.getItemValue("Value");
	var labels = [];
	for(var i=0;i<=labelList.length-1;i++){
		labels.push(labelList[i]);
	}
	var columnsWithTotals = doc.getItemValue("ColumnsWithTotals");
	var totalLabels = [];
	for(var i=0;i<=columnsWithTotals.length-1;i++){
		totalLabels.push(columnsWithTotals[i]);
	}
	createWorkbookStreamWithLabels(wbName,sheetName,colFields,viewName[0],labels,totalLabels);
}
}]]></xp:this.action>
		</xp:eventHandler>
	</xp:button>
	<xp:br></xp:br>
</xp:view>

Create your Export Stream Library

Due to the length of this post, the library is in a second post. I’ve also added a sample database.

Categories: Java, Server-Side Javascript, Xpages | Tags: , , , , , | 7 Comments

Using XPage ACLs to limit access

Among the items I’d been working on for the presentation at the DCLUG earlier this month was an evaluator’s page.

Now before I get into the code snippet, let me lay out a bit more of the design concept to put this in context. Users would anonymously create grant requests via browser, then internal users would assign evaluators with their Notes clients, and those evaluators would login via browser to fill out their assessments of the requests. So, I needed to come up with a way to force the logins and inhibit anyone who wasn’t authorized from seeing the evaluators work lists or assessments.

Notes does this wonderfully well, but as I’m breaking new ground with XPages, I had to search around for clues on how to do it. After a while, I did finally find something on my go-to source, Stack Overflow. (Thanks to Matt White and AndrewGra!)

<xp:this.acl>
    <xp:acl>
       <xp:this.entries>
          <xp:aclEntry type="ANONYMOUS" right="READER"></xp:aclEntry>
          <xp:aclEntry type="DEFAULT" right="EDITOR"></xp:aclEntry>
       </xp:this.entries>
    </xp:acl>
 </xp:this.acl>

While this is nice, it also opened me to wondering about all the other options of the aclEntry control. There are 5 parameters to be considered. The two required parameters are intriguing.

type – string – Defines type of entry, valid values are USER, GROUP, ROLE, ORGUNIT, ORGROLE, DEFAULT, ANONYMOUS

While both ORGUNIT and ORGROLE are deprecated, the other 5 are quite familiar. If you set type to either DEFAULT or ANONYMOUS, you don’t supply any of the name values, since these are effectively “unnamed” access types. For USER, GROUP and ROLE, you must supply name values.

right – string – Defines rights for this entry, valid values are NOACCESS, READER, EDITOR

On an XPage, you really are only concerned about end-user access, so it’s either none, reading or editing. Distinguishing between Author and Editor depends on the ACL and whether their are Author names fields on the source documents, so not something one wants the XPage monkeying around in anyway.

Since neither DEFAULT nor ANONYMOUS requires any name information, the next two parameters are optional.

fullName – string – Defines users full name e.g. John Smith

name – string – Defines entry name

There’s a real lack of clarity here as to what goes where, in particular, what to do about ROLES. Is it a name or a fullName. Do you use brackets [ ] or just the text? So, I fiddled around. I’d almost figured out how to implement ROLES myself when I searched just a bit more and found Russ Maher’s guidance. Basically, put brackets around the name value and straight text for the fullName. Here’s my ACL to limit the XPage to only those users with the Evaluator role.

<xp:this.acl>
	<xp:acl>
		<xp:this.entries>
			<xp:aclEntry type="ANONYMOUS" right="NOACCESS"></xp:aclEntry>
			<xp:aclEntry type="DEFAULT" right="NOACCESS"></xp:aclEntry>
			<xp:aclEntry fullName="Evaluator" right="EDITOR" type="ROLE" name="[Evaluator]">
			</xp:aclEntry>
		</xp:this.entries>
	</xp:acl>
</xp:this.acl>

The final parameter is also optional.

loaded – boolean – Specifies whether or not the tag instance should be created when the page is loading. Value defaults to ‘true’.

This one is obviously for people far more clever than I. I suppose that if you want the ACL to only take effect after some other event that trips the loaded flag to true or something. I’m not real sure. It is inherited from com.ibm.xsp.BaseComplexType if that helps you figure it out.

Just as form access and creation controls are very sneaky, these are as well. Since the properties don’t show up in the nice little properties box for the XPage and you have to hunt into either the source or the All Properties tab (under data), they are sure to create some trouble-shooting issues, especially when you’re new to XPages. So, I’d recommend you make sure to put these at the top of your XPages and I would be careful about using them on custom controls, unless you do everything in your custom controls, simply because of the lack of visibility.

Hope that helps someone looking for help with security in their XPages. I’m pretty happy with how easy it turned out to be to code it.

Categories: Security, Xpages | Tags: , , , , , , , , , | 4 Comments

Create a free website or blog at WordPress.com.

%d bloggers like this: