In this chapter
Deploying RAS Environments 856
This chapter covers the capability of the Report Application Server (RAS) SDK to create and modify reports. Topics include
The RAS APIS are available in two different environments—as a service of the BusinessObjects Enterprise framework or as part of Crystal Reports Server. For the remainder of the chapter, any reference to functionality being provided in BusinessObjects Enterprise automatically implies that the same functionality is available in Crystal Reports Server, unless otherwise noted.
BusinessObjects Enterprise provides a framework for delivering enterprise reporting. RAS adds the capability for users to modify reports stored in BusinessObjects Enterprise. In this scenario, the BusinessObjects Enterprise framework manages the RAS. Multiple instances of the RAS can be added and BusinessObjects Enterprise will load balance between them.
As mentioned previously, the RAS SDK JAR files can be found in the jar folder by default. Copy the RAS and BusinessObjects Enterprise .jar files to the appropriate folder on the application server being used. If you are using Apache Tomcat, for example, move the .jar files to the Web application’s WEB-INFlib folder. Configuring a Web server to access the SDK JAR files might take additional steps, detailed in the installation help files provided with the RAS.
Options for displaying and logging exception information can also be specified. These tasks can be performed by modifying the web.xml file (located by default in the WEB-INF directory of your Web application) as follows.
Three options exist for displaying exception information to the user. Setting the crystal_exception_info
parameter to one of the following values determines how exceptions are handled:
The following code shows an example of the exception display configuration:
The crystal_exception_info
parameter is short by default. Modifying exception.css specifies the style and formatting of short messages.
The option to turn exception logging either on or off can be set with the crystal_exception_log_file
parameter. The exception information output to the log file will be in the long format regardless of the setting of the crystal_exception_info
parameter. The following code shows an example of the exception logging configuration:
When setting the parameter to the desired path of the log file, by default, exceptions are not logged.
This section covers the common programming tasks associated with the RAS SDK. Although the SDK provides many capabilities, some of the following tasks are common to most programming exercises and are central to the SDK.
Initiating a session with the RAS is the first step in programming with the RAS SDK. In this step, a specific RAS can be specified for use; otherwise, the system selects one from the RASs listed in the clientSDKOptions.xml
file using a round-robin method. Initializing a RAS session by specifying a machine name at runtime is shown in the following code:
All ReportClientDocument objects created from the same ReportAppSession communicate with the same RAS.
A report can be opened first by creating a new ReportClientDocument
object and specifying the ReportAppServer. Then the open method can be used to open a report. This method takes two parameters:
See the OpenReportOptions
class for valid report options.
Reports are loaded from the report folder found at Program FilesCrystal DecisionsReport Application Server 10Reports
by default.
The following code opens a report:
The previous chapter explained how to view reports using RAS. Creating and modifying those reports using the RAS SDK will be the focus of the remainder of this chapter.
A report can be modified after creating and opening a ReportClientDocument by using the report’s controllers. The only way to modify reports and ensure that the changes are synchronized with the server is to use controllers. Although the report’s fields can be accessed directly through the DataDefinition
property, any changes made will not be committed. This section explains how to add a field to a report.
A field is usually selected by name. The DatabaseController can be used to retrieve the object that represents this field given a database field’s name or its table’s name. Another method of accessing a table’s fields is using the ReportClientDocument’s Database
property. Here you use the DatabaseController.
The DatabaseController contains a collection of database tables that are available to the report and might be accessed using the getDatabaseController
method of ReportClientDocument. Each table contains a collection of DBField objects.
All tables and fields that are listed by DatabaseController.getDatabase() are not retrieved when the report is refreshed; that is, they are available for report design but might not actually be part of the report’s data definition.
A method called findFieldByName
is shown in the following sample code snippet. This method returns a field given its fully qualified field name in the form: <TableAlias>.<FieldName>. The table alias is used as a qualifier and it is assumed that a period is used to separate the table alias from the field name.
This method uses the following key methods:
Tables.findByAlias
finds the index of a particular table when given its alias. Given the index, the desired Table
object can be retrieved from the collection.Fields.find
finds the index of a field in a table’s Fields collection when given the name of the field.After you obtain the Field
object that you want to add, the field can be added to the report so that it is processed and displayed when the report is run. This is done via the DataDefController, which is used to modify the report’s data definition and contains a subcontroller called the ResultFieldController. This subcontroller is used for modifying fields that have been placed on the report and that are processed at runtime. The fields that are shown on the report belong to the ResultFields collection. A new database field will be added to the ResultFields collection in this step.
The ResultFields collection can contain other types of field objects such as parameter fields, formula fields, and summary fields in addition to DBField objects. Like DBFields, the ResultFieldController can add these fields to a report. Unlike DBFields, only the DatabaseDefController’s DataDefinition
property, and not the DatabaseDefController’s Database
property, can retrieve these fields.
A field being added to the ResultFields collection is shown by the following code:
The parameter 1 indicates that the field is to be placed at the end of the collection. As a result of this code, the new field displays on the report and is processed when the report is refreshed.
The fields that have been added to a report are stored in the ResultFields collection and can be retrieved using the following sample method:
With the full name of the field, you can use a DatabaseController to retrieve the DBField
object.
When you’ve found the field you want to remove, use the ResultFieldController to remove it as follows:
In this code, fieldToDelete
is a DBField object. After the field is removed from the result fields using this method, the report ceases to display the field.
A new report can be created by first creating an empty ReportClientDocument as shown:
Because the newDocument
method of ReportClientDocument is provided for deployments that use an unmanaged RAS to access report (.rpt) files, it should not be deployed when using a BusinessObjects Enterprise RAS. Instead, when deploying with BusinessObjects Enterprise, the IReportAppFactory.newDocument
method should be used as in the previous code.
Because the report is not actually created until tables are added, after creating an empty ReportClientDocument, details such as the new report’s tables and the fields used to link them should be added.
However, before adding the tables to the new report, the table objects must first be retrieved from the source report. This can be accomplished in two ways: using the DatabaseController
object and using the Database
object, both of which are available from the ReportClientDocument
object. The ensuing code iterates through all the tables in an open report and prints the tables’ aliases:
Because controllers are the only objects that can modify the report’s object model, a controller must be used to add tables to a report. The following code retrieves the report’s DatabaseController and adds a table.
The addTable
method of the DatabaseController adds a table to the report. The addTable
method takes two parameters:
Table
object you want to addTableLinks
object that defines how the table being added is linked with other tablesTables must be linked after they have been added to the report. To link two tables, first create a new TableLink
object, set the properties of the TableLink
, and then add the TableLink
to the report definition.
Linking two tables using an equal join is illustrated by the following code:
These newly linked tables can be used as the report’s data source. However there have been no visible objects added to the report, so when the report is refreshed, it will be blank.
To add a group, you must know which field is being grouped on. For information on working with fields, see the “Adding a Field to the Report Document” section earlier in this chapter. Because not all fields can be used to define a group, use the canGroupOn
method of GroupController to check whether a field can be used for grouping. If canGroupOn
returns true, the field is an acceptable field to use for grouping. The next example demonstrates a function that adds a new group to a report:
Here the group was added to the end of the Groups collection by setting the index to -1, which means that the new group becomes the innermost group. When a new group is added, a new sorting definition is also added which will sort the records according to the group’s condition field and group options. An additional reflection of adding the new group is the group name field appearing on the group’s header. Fields added to the group header are not added to the ResultFields collection. When the group is removed, the group name field is also removed.
Using the SortController adds a new sorting definition to a report. The SortController can add any kind of sorting definition, including a Top N sort. Adding a Top N sort requires that a summary has first been added.
Next you demonstrate how to add a sort to the report by taking a Fields collection and adding a sorting definition based on each field in the collection:
When the new Sort
object is added, it is added to the end of the collection, indicated by the -1 argument, which designates that the records will be sorted on this field after all other sorting definitions. The SortDirection
class indicates the direction of the sort. The static objects SortDirection.ascendingOrder
and SortDirection.descendingOrder
are the only values that can be used for a normal sort. The other values are used for a Top N or Bottom N sort. See Adding a Top N sorting definition in the SDK documentation for additional details.
The SummaryFieldController adds a new summary field. To determine if a field can produce a summary, the SummaryFieldController’s method canSummarizeOn
is called. Here you add a summary to a group:
After creating a summary field, set the following properties before adding it:
SummarizedField
—The field used to calculate the summary.Group
—The group for which the summary will be calculated.Operation
—The operation used to calculate the summary. One of the static objects defined in the SummaryOperation class.Filters are used in record selection and group selection. The filter is initially a string written in Crystal formula syntax. The record selection formula is then parsed into an array of FilterItems
stored in the Filter
object’s FilterItems
property. The string is broken up into data components and operator components that act on the data. These components are stored as FieldRangeFilterItem
and OperatorFilterItem
objects respectively, which are stored in the FilterItems collection in the same order that they appear in the formula string. Re-ordering the objects in the array changes the functionality of the formula. In summary, the FieldRangeFilterItem
is an expression that is joined with other expressions using an OperatorFilterItem
.
For instance, consider a simple record selection formula such as
{Customer.Name} = "Bashka Futbol" and {Customer.Country} = "USA".
This results in only the records that have a name equal to “Bashka Futbol” and a country of the USA. The result is stored in the FreeEditingText
property. After this string is parsed, the FieldRangeItems collection contains two FieldRangeFilterItem objects because there are two data items used to filter the records. The OperatorFilterItem is used to indicate how two primitive expressions are combined, so it is now equal to and.
The FieldRangeFilterItem contains three properties:
Operation
—This property indicates the operation performed in the primitive expression; in both cases, it is the equals operator.RangeField
—The RangeField
property indicates the comparator field used in the expression. Because not all fields are suitable to filter records and groups, use the canFilterOn
method in the RecordFilterController and the GroupFilterController to determine whether a field can be used for a particular filter.Values
—The Values property indicates the comparison values in the expression. In this example, it is the strings “USA” and “Bashka Futbol”. This property has one ConstantValue object that stores “USA”.After the file is opened, and the filters parsed, the FreeEditingText
property that stores these strings is cleared and the FilterItems
populated. Conversely, if the formula is too complex, the FilterItems
collection property remains empty and the FreeEditingText
property populated. When altering a filter, you have two options: modify the FreeEditingText
property or the FilterItems
property.
If you use only one property to modify the filter, the other will not be automatically updated, however. For instance, you would modify the FreeEditingText
property, but this will not necessarily be parsed again to repopulate the FilterItems
. You should use only one of these properties per session.
Use a controller to ensure that modifications are saved. The GroupFilterController and the RecordFilterController modify the group formula and record formula respectively.
A FieldRangeFilterItem contains a primitive comparison expression. Its most relevant properties are
Operation
RangeField
Values
The Operation
and RangeField
properties usually contain a constant. However, the Values
property stores either ConstantValue objects, which don’t need evaluation (such as 1, 5, or “Stringiethingie”), and ExpressionValue objects, which do need evaluation (such as “WeekToDateSinceSun,” 4/2, and so on).
The following section of code defines the expression {Customer.ID > 2}. Note how it creates a new ConstantValue
object for the number 2 and adds it to the Values collection:
All fields cannot be used in a filter formula (for example, you can’t use BLOB fields). Use the canFilterOn
method, which is located in either the RecordFilterController or the GroupFilterController, to verify that a field can be filtered on. You must also verify that the constant data type is the same as the field. In the previous example, constantValue must not be a variant and corresponds to the data type used in the comparison.
The following example assumes the same expression as defined in the preceding example, but concatenates to the filter using the OR operator. Assume the filter would look like this: {Customer.ID} > 2 OR {Customer.name} = "Arsel"
. To the code above you would add:
The filterItems
parameter is a FilterItems collection. It stores FilterItem objects. In the two examples, both a FieldRangeFilterItem
object and an OperatorFilterItem
object were added to this collection. Both of these objects inherit from FilterItem, making this possible.
After defining the filter, you add it to the report. Filters can be used in two places: group selection and record selection. The GroupFilterController and RecordFilterController, which can be accessed via the DataDefController
object, modify their respective filters.
You can also obtain the filters from the GroupFilter
and RecordFilter
properties in the DataDefinition, although they can only be modified with a controller.
FilterController provides these methods for modifying a filter:
addItem
—This method adds an expression or an operator to the existing filter.modify
—This method replaces the current filter with a new or modified one.modifyItem
—This method modifies a filter element.moveItem
—This method moves the filter element around the filter array.removeItem
—This method deletes a filter element.In the following code, the modify method is used because a new filter has already been defined. Assume that there is a ReportClientDocument
object and that you have opened a report already:
Parameters enable end users to enter information to define the report behavior. Parameters have specific data types just like any other field: string, number, date, and so on. Parameters also are divided into two basic types: discrete and ranged. A discrete parameter value is one that represents a singular value such as 9, “Nur”, 1863, True, and so on. Ranged values represent a particular span of values from one point to another such as [9..95], [4..6], [“Alpha”, “Omega”]. The lower bound value of the range must be smaller than the upper bound. Some parameters support more than one value: They effectively contain an array containing many values.
Parameters have default values and the user can be forced to select from them. You can also provide default parameters but allow users to enter their own values. Default values are stored in the ParameterField.DefaultValues
property. Selected values are stored in the ParameterField.CurrentValues
property.
Parameters support many more features than those covered here. For a complete list of features, see the ParameterField
class in the SDK documentation.
The parameters are exposed in the SDK by the DataDefinition’s ParameterFields
class. The ParameterFields
class inherits from the Fields
class. For example the name of a parameter is obtained using this method:
The getDisplayName
method is used for UI purposes and so is not a unique identifier. The getFormulaForm
method can be used to retrieve a unique identifier. getDisplayName
and getFormulaForm
are not documented under the ParameterField
class because they are inherited from Field.
Because parameter values might be either discrete or ranged, and default values might only be discrete, there are two different objects to represent these: ParameterFieldDiscreteValue
and ParameterFieldRangeValue
. Both of these objects inherit from ParameterField
. You must understand the type of the parameter to know what kind of parameter values it contains. For example, the following code determines if the parameter is of a ranged or discrete type:
Check the parameter’s type before you try to print the parameter’s values. You must determine the type so you can retrieve the correct field. Trying to access the EndValue of a discrete value will cause a runtime error because no EndValue exists. The previous code example determines whether the parameter value is an instance of IParameterFieldRangeValue to determine what kind of values it will have. For parameters that support both discrete and ranged values, however, you must verify the type of the parameter by using getValueRangeKind
method. The following code checks the parameter type and calls a secondary function to handle the correct type and build a table of parameters:
The ParameterFieldController, which can be found in the DataDefController, enables you to change parameters. To modify a parameter field in the report, you copy the field, modify the copy, and then have the controller modify the original based on changes made to the copy. For instance here you demonstrate this by changing a default discrete value:
Use the ParameterFieldController to add new parameters. You do this the same way as adding any other fields to the report: A new field is created, its fields are set, and it is added using a controller. Here you define a new, discrete, string parameter and add it using the controller:
Adding a parameter using the Parameter field controller does not place the parameter on the report, so the user is not prompted for the parameter when the report is refreshed. To prompt the user, either use it in a filter, or add it by using the ResultFieldController.
Handling parameters involves many important details. When using parameters keep the following points in mind:
Failing these tests results in a runtime error.
The ChartObject, which represents a chart in the RAS SDK, inherits variables and methods from the ReportObject. Remember that the report that you open is represented by the ReportClientDocument, not the ReportObject.
The ChartObject’s properties determine the chart’s appearance and where it shows on the report.
Here you focus on three ChartObject properties:
ChartDefinition
indicates the chart type and the fields charted. The chart type can be a Group or Details type.ChartStyle
specifies the chart style type (such as a bar chart or a pie chart) and the text that appears on the chart (such as the chart title).ChartReportArea
is where the chart is located (for example, the report footer).The following two sections show how you can use these ChartObject
properties to create a chart. You must first specify the fields on which you want your chart to be based on. To do this, create a ChartDefinition
object, which will then be added to the ChartObject
with the ChartDefinition
property.
The ChartDefinition
object determines the type of chart that appears in the report and sets the fields to be displayed. A simple, two-dimensional chart displays two types of fields:
Below the chart added is a Group type (see the ChartType
class), so the ConditionFields and DataFields that are being charted on are group fields and summary fields respectively.
Add the first group field in the Groups collection to a Fields collection. This field is retrieved with the ChartDefinition’s getConditionFields
method.
Adding two groups as ConditionFields enables you to create a 3D chart. Because one value is required for the x values, the next value drives the z-axis.
After you have added ConditionFields to the ChartDefinition, add the DataFields. In a Group type chart, the DataFields are summaries for the group fields that you added as ConditionFields.
Adding DataFields is similar to how you added ConditionFields. For example, you use the name of the summary field that the user has selected to locate the desired field in the SummaryFields collection and add this field to a Fields collection. You then accessed the summary field with the ChartDefinition’s DataFields
property:
Here you use the LongName of the summary field. The LongName contains the type of summary, for example, a sum or a count, and the group field that it applies to. For example:
Sum of (Customer.Last Year's Sales, Customer.Country)
In general you will want to use a field’s LongName instead of its ShortName or Name to avoid confusion as the ShortName or Name might be the same for several fields.
After the fields are defined, they are added to the ChartObject with the ChartDefinition
property. The following code uses the ChartObject’s ChartStyle
property and ChartReportArea to specify the chart style type, the chart title, and the location of the chart:
In this example, the first chart that you add will appear 50 points below the top of the report area in which the chart is located. (These fields are measured in twips, and 20 twips = 1 font point, so 1000/20 = 50 points.) Adding another chart to the same report area places it over the first chart because the formatting for report objects is absolute. The first chart remains hidden until the second chart is removed.
Now add the chart using the ReportObjectController’s add
method. This method takes three parameters: the ChartObject, the section to place it in, and the position in the ReportObjectController collection where you want to add the chart. An option of 1 for the index adds the chart to the end of the array. Return the ReportObjectController by the ReportDefController’s getReportObjectController
method:
If you want to modify an existing chart, you can use the clone method to copy the chart, make the desired changes, and then call the modifyObject
method using the original chart and the newly modified chart as parameters.
A subreport is a report within a report. It can be a free-standing or linked report in the main report. A subreport can have most of the characteristics of a report, except the followings:
With RAS SDK version XI existing subreports can be modified to the same level of details as in ReportClientDocument. There two classes and one interface that define most methods and properties about subreports, they are:
From ReportClientDoccument
object you call method getSubreportController()
to get a SubreportController
object. SubreportController
class exposes two methods to import an existing report as a subreport with one setting intelligent default values for left, top, width and height of the subreport object and the other one using user-defined values for top, left, width and height.
In this example, reportURL is the URL of the report to import. The URL must be an absolute physical path accessible to the client RAS SDK. Web URLs and file paths managed by BusinessObjects Enterprise are not currently supported. If the reportURL is empty, a blank report will be imported. In this import subreport method intelligent default values for left and top are 0 in twips, that is, the relative left and top positions of the subreport to the section where it is imported are 0. The default width and height of the subreport are the width and height of the section where it is imported.
You can modify the subreport through the subRptClientDocument
object by accessing the report data definition and controllers. The subreport object uses methods similar to those of the ReportClientDocument so that all controllers and properties are not different from the main document; therefore end user confusion is reduced.
A SubreportLink
object specifies a link between a subreport and the enclosing main report. For example, you have customer data in a primary report and then use subreports to show the orders for each customer, to coordinate the data in the primary report with the data in the subreport so that the orders in each subreport match up with the correct customer you need to specify a field that is common to both the subreport and the primary report, such as Customer ID. A subreport link allows you to link the two common fields so that records from the primary report can be match up to those in the subreport. SubreportLinks
object is a collection of SubreportLink
objects.
RAS SDK version XI allows server-side printing. Server-side printing allows you to print reports from machines that run the RAS SDK application server.
To print a report from the machine that runs the RAS SDK application server you need to specify print options. Use the following code:
Then you get PrintOutputController from ReportClientDocument. PrintOutputController is used to print a report with RAS SDK server-side printing. It also defines methods to export reports to specific format, such as RTF, editableRTF, or PDF and to modify various formatting options.