Documentation 1.3

What is infiniquery
How it works
Quick start
More configuration
Tag reference (for infiniquery-config.xml)
Customize the query results presentation
Setting up the backend
Using Infiniquery without Spring
Security
Backward compatibility
Interoperability concerns
Cross-browser compatibility
Portability across databases
3rd party dependencies
Extensibility concerns

What is infiniquery

Infiniquery is a software development tool, that dramatically simplifies the construction of data search screens.

The framework has two goals:

  1. Simplify the development and maintenance: Building a complex search screen, with infiniquery, is as easy as writing a simple XML file. It doesn't require any compileable code, nor any UI skills or effort.
  2. Simplify and dramatically enhance end-users experience - a screen generated by infiniquery is a full-power, customized, intuitive, visual query language.

Infiniquery can be used in any Java-based system that has a web front-end and a JPA persistence integration layer.

How it works

Infiniquery introspects your backend, based on Java Reflection and it automatically generates your frontend, starting from the structure of the backend.

Because the human brain is single-threaded and acts sequentially, infiniquery adapts to this matter and offers the natural experience that any end user has ever dreamed to. It is extremely intuitive and supports extremely complex query constructions, without adding extra-complexity. In most cases, users need just a few mouse clicks, to build and execute a search.

Infiniquery introspects your persistent entities, via Java Reflection, and automatically generates web front-end components that match the input types.

It also guesses the user's will, based on user's previous actions, and generates exactly what the user expects, in a fully intuitive and self-explanatory manner.

Quick start

This section describes the minimum steps necessary to introduce an infiniquery functionality in your Java-Web application.

Step 1

Download the framework binaries from the download page.

Step 2

Add infiniquery backend depedencies to your application's classpath.

        <dependency>
            <groupId>org.infiniquery</groupId>
            <artifactId>infiniquery-core</artifactId>
            <version>1.3</version>
        </dependency>
        <dependency>
            <groupId>org.infiniquery</groupId>
            <artifactId>infiniquery-web-spring</artifactId>
            <version>1.3</version>
        </dependency>
	  
Step 3

Create the infiniquery-config.xml file and place it in the root of your classpath (see the DTD for more information).
Example:

	<query-context>
		<findKeyword>Search for</findKeyword>
		<results-limit>1000</results-limit>
		<entities>
			<entity className="org.infiniquery.demoapp.entities.Employee" displayName="Employees">
				<attribute attributeName="firstName" displayName="first name"/>
				<attribute attributeName="lastName" displayName="last name"/>
				<attribute attributeName="birthDate" displayName="birth date"/>
			</entity>
			<entity className="org.infiniquery.demoapp.entities.Department" displayName="Departments" >
				<attribute attributeName="departmentName" displayName="department name"/>
				<attribute attributeName="status" displayName="status"/>
			</entity>
		</entities>
	</query-context>
	  
Step 4

Add JavaScript and CSS dependencies to your web page header:

    <link rel="stylesheet" type="text/css" href="infiniquery.css" />
    <script type="text/javascript" src="datetimepicker/datetimepicker_css.js"></script>
    <script type="text/javascript" src="infiniquery.js"></script>
	  
Step 5

Add a div to serve as placeholder for infiniquery, in your web page. Give it the ID "dynamicQueryDiv" (this is a predefined identifier that the framework knows about).

Example:
	    <div id="dynamicQueryDiv"></div>
	  
Step 6

If your application has a specific root path, ensure the endpoint URLs point to the corect addresses after page loading and before infiniquery.startQueryCreation() is called.

Example:
		var applicationRootURL = "/infiniqueryDemoApp";
		infiniquery.findKeywordEndpoint = applicationRootURL + infiniquery.findKeywordEndpoint;
		infiniquery.entitiesEndpoint = applicationRootURL + infiniquery.entitiesEndpoint;
		infiniquery.entityAttributesEndpoint = applicationRootURL + infiniquery.entityAttributesEndpoint;
		infiniquery.entityAttributeOperatorsEndpoint = applicationRootURL + infiniquery.entityAttributeOperatorsEndpoint;
		infiniquery.entityAttributeOperatorValueEndpoint = applicationRootURL + infiniquery.entityAttributeOperatorValueEndpoint;
		infiniquery.conditionSeparatorValuesEndpoint = applicationRootURL + infiniquery.conditionSeparatorValuesEndpoint;
		infiniquery.compileQueryEndpoint = applicationRootURL + infiniquery.compileQueryEndpoint;
	  
Step 7

Add a trigger for starting the query creation.

Example:
		<body onload="infiniquery.startQueryCreation()">
	  

More configuration

Tag reference (for infiniquery-config.xml)
NameRequiredTypeDescriptionAttributes
query-context true   Root tag of infiniquery-config.xml. The most outer tag, containing everything inside infiniquery-config.xml.  
findKeyword true String The custom word (or expression) to start the query in the UI (this should be an user friendly analog for the "select" keyword of SQL). Examples: "Find" or "Search for" or "Select" etc.  
results-limit true Integer The number of maximum query results to fetch. This is like a protection number. The purpose is to avoid OutOfMemoryError in case of user created queries that ar ran against large databases.  
entities true   Tag for grouping entity tags inside its body.  
entity at least one   Represents a JPA entity to be exposed in infiniquery queries. May reside only inside an <entities> tag. className (required) - fully-qualified name of the Java class of the JPA entity

displayName (required) - an user-friendly alias for the entity, to be displayed in the UI

roles (optional) - a security related tag, that restricts access to the entity, allowing only specific user roles; values, if more than one, should be comma-separated.

additionalFilter (optional) - a query condition, expressed in JPQL syntax, that can refer global attributes with a syntax like ${someAttributeName}. The global attributes are made available by overriding the getGlobalScopeAttributes() method of the SecurityService. This feature allows to restrict the access of the logged user only to specific subsets of data. Refer to the Security section for more details.
attribute at least one   Represents an attribute of the JPA entity, that will be exposed as filter option in infiniquery queries. attributeName (required) - name of the java attribute of the JPA entity

displayName (required) - an user-friendly alias for the attribute, to be displayed in the UI, as both filter option and query results column

scope (optional; default="all") - values: "query"|"display"|"all" - tells the framework if the attribute should be displayed only in the query, only in the results list, or in both.

displayOnly (deprecated; optional; default="false") - boolean attribute telling the framework if the attribute should be displayed only in the results list; if the value is set to "false", then the attribute will be shown both as a query filter option and as a column in the query results table.

roles (optional) - a security related tag, that restricts access to the attribute, allowing only specific user roles; values, if more than one, should be comma-separated.

possibleValuesQuery (optional) - applicable only if we want to allow only a set of pre-defined values for the attribute, in the query; should be a valid JPQL query that returns a collection of entities of the suitable type; if specified, then possibleValueLabelAttribute and possibleValueLabelAttributePath are required.

possibleValueLabelAttribute (optional) - works together with possibleValuesQuery; its value should specify the attribute of the possible values JPA entity type, that should be used by infiniquery in the UI to show possible values suggestions;

possibleValueLabelAttributePath (optional) - works together with possibleValuesQuery; its value should specify the path in the Java object tree to the label attribute, starting from the entity type down to the attribute label.
Tip 1 - customize the query results presentation

Sometimes, your JPA entities contain properties in a form that needs some formatting before being shown to the end-user. For example, if you have a java.sql.Timestamp attribute, it may reach the query results screen as a long value representing an epoch time. Also, in some cases, you may want the results table header text to be a little different than the attribute names you have defined for the query filters. If you find yourself in any of these situations, you may overwrite the default infiniquery functions that build the query results cells.

Example (you may add something like the following in your web page header):

    <script type="text/javascript">
        function onLoadHandler() {
        	infiniquery.startQueryCreation();
			
        	/**** Below we show that it is possible to add dynamic behavior into result table cells ****/
        	infiniquery.addResultsTableRow = function(table, rowIndex, object) {
                var row = table.insertRow(rowIndex);
                object = object["attributesMap"];
                for (var property in object) {
                    if (object.hasOwnProperty(property)) {
                        var cell = row.insertCell(0);
                        cell.innerHTML = decodeOutputData(property, object[property]);
                    }
                }
            }
        	infiniquery.addResultsTableHeader = function(table, object) {
                var row = table.insertRow(0);
                object = object["attributesMap"];
                for (var property in object) {
                    if (object.hasOwnProperty(property)) {
                        var cell = row.insertCell(0);
                        if("id" == property) {
                        	cell.innerHTML = "file";
                        } else {
                        	cell.innerHTML = property;
                        }
                    }
                }
            }
	    	function decodeOutputData(property, value) {
	    		if(! value) {
	    			return value;
	    		}
	    		if("last name" == property) {
	    			value = value.toUpperCase();
	    			return value;
	    		} else if("productivity factor" == property) {
	    			if(value >= 3) {
	    				return value;
	    			} else {
	    				return "" + value + "";
	    			}
	    		} else if("age" == property) {
	    			return value + " years";
	    		} else if ("more info" == property) {
	    			return "download";
	    		} else {
	    			return value;
	    		}
	    	}
		}
		
		//here we also customize the text on the close button from the query results
		document.getElementById("infiniqueryResultsDivCloseButton").value="Close Results";
    </script>
	  
Tip 2 - Setting up the backend

infiniquery-core is completely agnostic of the Spring Framework (read Tip 3 below to see why). Thus, setting up the beans can be done either programatically or via setter injection in Spring configuration. You can also customize these beans, extend or replace them with your own implementations. Example of bean wiring:

	import java.util.HashSet;
	import java.util.Set;
	import javax.persistence.EntityManager;
	import org.infiniquery.connector.JpaConnector;
	import org.infiniquery.service.DefaultDatabaseAccessService;
	import org.infiniquery.service.DefaultQueryModelService;
	import org.infiniquery.service.SecurityService;
	import org.infiniquery.web.spring.controller.QueryModelController;
	  
[...]
    	ConfigurableApplicationContext appContext = SpringApplication.run(Application.class, args);
    	
    	SecurityService securityService = new SecurityService() {
			@Override
			public Set getCurrentUserRoles() {
				//This is a sample hardcoded implementation that says current user is ADMIN and MANAGER.
				//In a real production application, you should have an implementation that returns
				//the role(s) of the logged user, taken from the session or from wherever your application
				//keeps record of the logged user.
				return new HashSet() {{
					add("ADMIN");
					add("MANAGER");
				}};
			}
    	};
    	
    	QueryModelController queryModelController = appContext.getBean(QueryModelController.class);
    	DefaultQueryModelService queryModelService = new DefaultQueryModelService();
    	DefaultDatabaseAccessService databaseAccessService = new DefaultDatabaseAccessService();

/*
    	//inject your own entity manager if you don't want to go with a different one in infiniquery than the rest of your app.
    	//If you leave these lines commented, infiniquery will create its own entity manager.
    	EntityManager entityManager = appContext.getBean(EntityManager.class);
    	JpaConnector.setEntityManager(entityManager);
    	databaseAccessService.setEntityManager(entityManager);
 */
    	
    	queryModelService.setDatabaseAccessService(databaseAccessService);
    	queryModelService.setSecurityService(securityService);
    	
    	queryModelController.setQueryModelService(queryModelService);
	  

You can notice above the following beans:

Tip 3 - Using Infiniquery without Spring

In some software systems, you may want to avoid any compile and runtime dependency to the Spring Framework. In some situations, your architectural decisions may be around other REST APIs. In other cases your monolitic system may be hardly bound to an older version of Spring itself, that may be incompatible with some code from infiniquery-web-spring.

To address such kind of issues, infiniquery-core was build completely agnostic of the Spring Framework. Class org.infiniquery.web.spring.controller.QueryModelController is the only class in infiniquery-web-spring. This REST controller only exposes the service methods from infiniquery-core in a RESTful manner, for being accessible to the UI. Infiniquery is fully open-source (and released under a BSD 2-clause license); so, you are free to look into the code and rewrite this controller class in any technology that you want. This way, you can use only a dependency to infiniquery-core in your system. The code in the QueryModelController is short and trivial to rewrite (only about 100 lines of real code, excluding javadoc).

Security

As it generates flows that span the application layers, from the client front-end all the way to the database, a few concerns arise. To prevent those, infiniqury does a few things for ensuring your application's security:

Backward compatibility

All Infiniquery versions, released up to date, are backward compatible. All necessary efforts will be done to ensure that all future versions will maintain backward compatibility. I consider this to be one of the most important measures of the reliability of a framework over time.

Interoperability concerns

The front-end has its only external dependency to Tigra Calendar and does not affect the possibility to work alongside other javascript frameworks, in the same page.

The backend requires Java 7 or above.

Infiniquery supports popular data types such as Joda date-time in your persistent entities, withot forcing you to have a compile-time dependency to those libraries. For this purpose, infiniquery is translating such datatypes exclusively via Java Reflection.

Cross-browser compatibility

Portability across databases

Infiniquery is designed to work on any relational database where a standard Java Persistence API (JPA) integration layer works.

If you need to use it for applications that work with non-relational databases, our recommended approach is to either:

3rd party dependencies

We put efforts in keeping infiniquery clean from outer dependencies, in order to avoid future upgrade and backward compatibility problems of overlapping other frameworks that might already be used in our client applications. Still, there are a few 3rd party dependencies that have been necessary:

Extensibility concerns

Infiniquery can be easily extended with connectors to support other query languages than JPQL and apply to both relational or non-relational databases.

Infiniquery can be easily extended with infiniquery-web-somethingElseThanSpring to support other REST endpoints and not only (you can even build a different front-end to make it work in desktop applications, such as JavaFX).