Pass information to new web-browser window/tab being opened with BrowserWindowOpener in Vaadin 8 - parameter-passing

In Vaadin 8, we can let the user open a new tab/window within their web browser by clicking a button that has been associated with a BrowserWindowOpener. As discussed in the manual, a new UI is instantiated on our behalf. All we pass is a .class object, the class of our UI subclass to be instantiated for display in the new window/tab. For example:
BrowserWindowOpener opener = new BrowserWindowOpener( PersonDetailUI.class );
That works for me. My question is: How do I pass some information to that new UI object in that new window/tab?
For example, I might need to pass:
The ID number or UUID of a record to be looked up in a database.
A JavaBean object ready for display in a layout.
I see I asked about this same issue for Vaadin 7. Consider this an updated version of the Question for Vaadin 8. The only Answer there speculated about adding parameters to the URI of the new window. But that limits me to a small piece of text. I prefer to pass a smart object rather than a dumb string.

There are basically three approaches you can use, and combinations of these
Use URI parameters. As you mentioned, this is limited to String type data.
You can read the URI fragment in UI with e.g. String uriFragment = Page.getCurrent().getUriFragment();
You can read URI parameter using VaadinRequest.getParameter(), VaadinRequest is given as parameter in init(...) of main UI
UI's in different browser tabs share the same Vaadin session. That gives some tools, namely
You can use session attributes, i.e. VaadinSession.getCurrent().getAttribute(…) and VaadinSession.getCurrent().setAttribute(…)
If you use CDI or Spring, you can Inject / Autowire #VaadinSessionScoped bean. The instance is then bound to Session and hence shared between the tabs.
Read data from database (possibly using 1. and/or 2. as help for keys)

The Answer by Tatu Lund mentioned passing a URI parameter to the new window being opened.
Example app, passing URI parameter to new window
Here is a simple little demonstration app in Vaadin 8.5.0 to show that technique.
We start with three cats that we pretend to retrieve from a database. Each time the user selects a cat, we get the cat’s identifier, a UUID object. We generate a canonical 32-character hex string representation of that UUID. And we specify that as the parameter value to be passed with the parameter key cat_id. We specify that parameter by calling BrowserWindowOpener::setParameter.
Note that we do this on the selection of an item in the Grid listing cats. Because of browser restrictions in opening windows, the BrowserWindowOpener must be configured before the user clicks its button. We cannot run code in reaction to the button click, as far as I know.
The new browser window is populated with an automatically-instantiated subclass of UI. In this case CatUI is what we wrote. In CatUI, we get the URI parameter, extract the string representing the UUID, recreate the UUID object, and then pass that to our pretend database-service to fetch the cat in question. Then a layout’s fields are populated with the Cat object values. We don’t bother with data-binding the cat-to-layout as that is not the point of this demo.
Caveat: This is just a demo, and ignores issues that are not directly related to issue of passing information via URI parameter to a new window. For example, crucial issues of concurrency are ignored here but would not be in real work.
This demo consists of four class files, all pasted below.
MainUI (opened by default when app launches)
CatUI (opened when user clicks button)
Cat (business object, with name and id properties)
DatabaseService (pretend storehouse of cat records)
MainUI
package com.basilbourque.example;
import com.vaadin.annotations.Theme;
import com.vaadin.annotations.VaadinServletConfiguration;
import com.vaadin.server.BrowserWindowOpener;
import com.vaadin.server.VaadinRequest;
import com.vaadin.server.VaadinServlet;
import com.vaadin.ui.Button;
import com.vaadin.ui.Grid;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;
import javax.servlet.annotation.WebServlet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
/**
* This UI is the application entry point. A UI may either represent a browser window
* (or tab) or some part of an HTML page where a Vaadin application is embedded.
* <p>
* The UI is initialized using {#link #init(VaadinRequest)}. This method is intended to be
* overridden to add component to the user interface and initialize non-component functionality.
*/
#Theme ( "mytheme" )
public class MainUI extends UI {
private Grid< Cat > grid;
private Button button;
#Override
protected void init ( VaadinRequest vaadinRequest ) {
// Configure button to open now browser window/tab.
this.button = new Button( "Open in new window" );
BrowserWindowOpener opener = new BrowserWindowOpener( CatUI.class );
opener.setFeatures( "height=300,width=500,resizable" );
opener.extend( this.button );
opener.setParameter( "cat_id" , new UUID(0,0).toString()); // Send nil UUID (all zeros) if nothing selected.
System.out.println( "BWO URL: " + opener.getUrl() );
this.button.setEnabled( false );
this.grid = new Grid<>( Cat.class );
this.grid.setCaption( "Cats" );
List<Cat> cats = new DatabaseService().fetchAllCats() ;
this.grid.setItems( cats );
// Every time the user selects a cat in the Grid, assign that cat’s ID to our `BrowserWindowOpener`. This way our button is always prepared to open a window for the selected cat.
this.grid.addSelectionListener( event -> {
Set< Cat > selectedCats = event.getAllSelectedItems();
this.button.setEnabled( selectedCats.size() > 0 );
if ( selectedCats.size() > 0 ) { // If the user selected an item.
Cat cat = selectedCats.stream().findFirst().get();
opener.setParameter( "cat_id" , cat.getId().toString() ); // A UUID’s canonical presentation is as a 36-character hexadecimal string in five groups with HYPHEN-MINUS as delimiter.
} else {
opener.setParameter( "cat_id" , new UUID(0,0).toString()); // Send nil UUID (all zeros) if nothing selected.
}
System.out.println( "BWO URL: " + opener.getUrl() );
} );
this.grid.select( cats.stream().findFirst().get() ); // Select first item arbitrarily, to provoke the grid’s selection-listener above to fire.
button.addClickListener( e -> {
System.out.println( "BASIL opening now window" );
} );
final VerticalLayout layout = new VerticalLayout();
layout.addComponents( this.grid , button );
setContent( layout );
}
#WebServlet ( urlPatterns = "/*", name = "MyUIServlet", asyncSupported = true )
#VaadinServletConfiguration ( ui = MainUI.class, productionMode = false )
public static class MyUIServlet extends VaadinServlet {
}
}
CatUI
package com.basilbourque.example;
import com.vaadin.server.VaadinRequest;
import com.vaadin.ui.*;
import java.util.Optional;
import java.util.UUID;
public class CatUI extends UI {
private Cat cat = null;
#Override
protected void init ( VaadinRequest vaadinRequest ) {
// Retrieve a parameter from the URI of this UI/window.
String catUuidString = vaadinRequest.getParameter( "cat_id" ); // In the URI key-value parameters, "cat_id" is our key, and a UUID’s hex string is the expected value.
if ( null == catUuidString ) { // If we did not receive the UUID-string parameter we expected.
this.setContent( this.buildLayoutForNoCat( null ) );
return;
}
UUID uuid = UUID.fromString( catUuidString ); // Rehydrate the `UUID` from our passed hex string representing the UUID’s value.
Optional< Cat > cat = new DatabaseService().fetchCat( uuid );
if ( cat.isPresent() ) { // NULL check.
System.out.println( "uuidString: " + uuid + " and cat: " + cat.get() );
this.setContent( this.buildLayoutForCat( cat.get() ) ); // Retrieve the `Cat` object from our `Optional< Cat >` object by calling `get()` only after checking for NULL.
return;
} else { // Failed to find cat.
this.setContent( this.buildLayoutForNoCat( uuid ) );
return;
}
}
private Layout buildLayoutForCat ( Cat cat ) {
this.cat = cat ;
this.getPage().setTitle( "Cat details" );
// Have some content for it
TextField name = new TextField( "Name: " );
name.setWidth( 100 , Unit.PERCENTAGE );
name.setValue( this.cat.getName() );
TextField id = new TextField( "Id: " );
id.setWidth( 100 , Unit.PERCENTAGE );
id.setValue( this.cat.getId().toString() );
VerticalLayout layout = new VerticalLayout();
layout.addComponent( name );
layout.addComponent( id );
return layout;
}
private Layout buildLayoutForNoCat ( UUID uuid ) {
VerticalLayout layout = new VerticalLayout();
String message = "No cat found for the id: " + uuid;
Label label = new Label( message );
layout.addComponentsAndExpand( label );
return layout;
}
}
Cat
package com.basilbourque.example;
import java.util.UUID;
public class Cat {
private UUID id;
private String name;
public Cat ( UUID id , String name ) {
this.id = id;
this.name = name;
}
public UUID getId () {
return id;
}
public void setId ( UUID id ) {
this.id = id;
}
public String getName () {
return name;
}
public void setName ( String name ) {
this.name = name;
}
// Override `Object`.
#Override
public String toString () {
return "Cat{ " +
"id=" + id +
", name='" + name + '\'' +
" }";
}
}
DatabaseService
package com.basilbourque.example;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
// Pretending to be our gateway to a database.
public class DatabaseService {
static private List< Cat > cats;
{
DatabaseService.cats = List.of( // Produces an unmodifiable list. (A new feature in Java 9 and later.)
new Cat( UUID.fromString( "adf5c1a0-912e-11e8-9eb6-529269fb1459" ) , "Fluffy" ) ,
new Cat( UUID.fromString( "d37401c6-912e-11e8-9eb6-529269fb1459" ) , "Spot" ) ,
new Cat( UUID.fromString( "de29b6d8-912e-11e8-9eb6-529269fb1459" ) , "Lilly Mae" )
);
}
public List< Cat > fetchAllCats () {
return new ArrayList<>( DatabaseService.cats ); // Copy the list, then return.
}
public Optional< Cat > fetchCat ( UUID uuid ) {
return DatabaseService.cats.stream().filter( cat -> cat.getId().equals( uuid ) ).findFirst();
}
public static void main ( String[] args ) {
Optional< Cat > cat = new DatabaseService().fetchCat( UUID.fromString( "de29b6d8-912e-11e8-9eb6-529269fb1459" ) );
if ( cat.isPresent() ) {
System.out.println( "cat: " + cat.get() );
} else {
System.out.println( "No cat found." );
}
}
}

Related

How to get a widget to run with multiple actions from typoscript?

In creating a new uncached widget for login/logout/registering users in the Frontend, am unable to get it to work. How can I call two different controllers from typoscript (see code below)?
Am using TYPO3 9.5. Knowing how to create one is important because I'll need that info in creating many others for various uses. I have previously created a complex login system without widget/controller/action in TYPO3.
In ext_localconf.php, there is;
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin(
VendorName.ExtensionName,
PluginName,
[
'Frontend' => 'index',
'Account' => 'index,login,logout,register'
], [
'Account' => 'login,logout,register'
]);
Under folder structure Classes/Controller there is class VendorName\ExtensionName\Controller\AccountController which has;
class AccountController extends AbstractWidgetController {
/**
* #var array
*/
protected $supportedRequestTypes = [
Request::class,
WidgetRequest::class
];
public function initializeAction() {
}
public function indexAction() {
}
public function loginAction() {
return $this->view->assign('raw', 'Hello World');
}
public function logoutAction() {
}
public function registerAction() {
}
/**
* Handles a request. The result output is returned by altering the given response.
*
* #param \TYPO3\CMS\Extbase\Mvc\RequestInterface $request The request object
* #param \TYPO3\CMS\Extbase\Mvc\ResponseInterface $response The response, modified by this handler
*
* #return void
* #api
*/
public function processRequest(RequestInterface $request, ResponseInterface $response) {
#ActionController::processRequest($request, $response);
}
}
And in the ts file there is;
page = PAGE
page {
...
10 = USER
10 {
...
userFunc = TYPO3\CMS\Extbase\Core\Bootstrap->run
vendorName = VendorName
extensionName = ExtensionName
pluginName = PluginName
}
}
...
5 = USER_INT
5 {
userFunc = TYPO3\CMS\Extbase\Core\Bootstrap->run
vendorName = VendorName
extensionName = ExtensionName
pluginName = PluginName
controller = Account
action = login
}
When running this code, the PAGE ts produces the page using the Frontend controller index action which returns raw html through a fluid template. But when I add the USER_INT part, TYPO3 runs out of memory and displays a blank page.
Widgets are a type of ViewHelper used in Fluid templates. From what you describe, I think you want a plugin. Your Controller class needs to extend TYPO3\CMS\Extbase\Mvc\Controller\ActionController, not TYPO3\CMS\Fluid\ViewHelpers\Widget\Controller\AbstractWidgetController for that.

In log4j2, how to configure renameEmptyFiles to be false for the RollingFile appender?

I'm using log4j 2 and RollingFile appender:
<RollingFile name="mylog"
fileName="mylog.log"
filePattern="mylog.log.%d{yyyy-MM-dd}.log">
<PatternLayout>
<pattern>[%d] [%-5p] [%-8t] %F:%L %m%n</pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy interval="1"/>
</Policies>
</RollingFile>
The log files do get renamed daily. But the Javadoc of FileRenameAction class indicates there is an option renameEmptyFiles which is false by default so if a day's log is empty it deletes it instead of rename it appending the date to the file name. How to configure it to true since I'd like to have the log file even if it's empty?
I made a little plugin that offers the desired functionality. I simply extended the DefaultRolloverStrategy and replaced (as all of its fields are final) the RolloverDescription object that is returned from rollover(). I copied the static #PluginFactory code from DefaultRolloverStrategy as it's required for the Log4j 2 plugin system.
Here's the code:
import java.util.zip.Deflater;
import org.apache.logging.log4j.core.appender.rolling.DefaultRolloverStrategy;
import org.apache.logging.log4j.core.appender.rolling.RollingFileManager;
import org.apache.logging.log4j.core.appender.rolling.RolloverDescription;
import org.apache.logging.log4j.core.appender.rolling.RolloverDescriptionImpl;
import org.apache.logging.log4j.core.appender.rolling.action.Action;
import org.apache.logging.log4j.core.appender.rolling.action.FileRenameAction;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.lookup.StrSubstitutor;
import org.apache.logging.log4j.core.util.Integers;
#Plugin( name = "KeepEmptyFilesRolloverStrategy", category = "Core", printObject = true )
public class KeepEmptyFilesRolloverStrategy extends DefaultRolloverStrategy
{
private static final int MIN_WINDOW_SIZE = 1;
private static final int DEFAULT_WINDOW_SIZE = 7;
#PluginFactory
public static KeepEmptyFilesRolloverStrategy createStrategy( #PluginAttribute( "max" ) final String max,
#PluginAttribute( "min" ) final String min,
#PluginAttribute( "fileIndex" ) final String fileIndex,
#PluginAttribute( "compressionLevel" ) final String compressionLevelStr,
#PluginElement( "Actions" ) final Action[] customActions,
#PluginAttribute( value = "stopCustomActionsOnError", defaultBoolean = true ) final boolean stopCustomActionsOnError,
#PluginConfiguration final Configuration config )
{
final boolean useMax = fileIndex == null ? true : fileIndex.equalsIgnoreCase( "max" );
int minIndex = MIN_WINDOW_SIZE;
if ( min != null )
{
minIndex = Integer.parseInt( min );
if ( minIndex < 1 )
{
LOGGER.error( "Minimum window size too small. Limited to " + MIN_WINDOW_SIZE );
minIndex = MIN_WINDOW_SIZE;
}
}
int maxIndex = DEFAULT_WINDOW_SIZE;
if ( max != null )
{
maxIndex = Integer.parseInt( max );
if ( maxIndex < minIndex )
{
maxIndex = minIndex < DEFAULT_WINDOW_SIZE ? DEFAULT_WINDOW_SIZE : minIndex;
LOGGER.error( "Maximum window size must be greater than the minimum windows size. Set to "
+ maxIndex );
}
}
final int compressionLevel = Integers.parseInt( compressionLevelStr, Deflater.DEFAULT_COMPRESSION );
return new KeepEmptyFilesRolloverStrategy( minIndex,
maxIndex,
useMax,
compressionLevel,
config.getStrSubstitutor(),
customActions,
stopCustomActionsOnError );
}
protected KeepEmptyFilesRolloverStrategy( int minIndex,
int maxIndex,
boolean useMax,
int compressionLevel,
StrSubstitutor subst,
Action[] customActions,
boolean stopCustomActionsOnError )
{
super( minIndex, maxIndex, useMax, compressionLevel, subst, customActions, stopCustomActionsOnError );
}
#Override
public RolloverDescription rollover( final RollingFileManager manager ) throws SecurityException
{
RolloverDescription oldResult = super.rollover( manager );
// Fail fast (ClassCastException) if implementation of DefaultRolloverStrategy
// ever changes and uses a different Action type.
FileRenameAction oldRenameAction = (FileRenameAction) oldResult.getSynchronous();
FileRenameAction newRenameAction = new FileRenameAction( oldRenameAction.getSource(),
oldRenameAction.getDestination(),
true );
RolloverDescription newResult = new RolloverDescriptionImpl( oldResult.getActiveFileName(),
oldResult.getAppend(),
newRenameAction,
oldResult.getAsynchronous() );
return newResult;
}
}
To use this class, simply reference it in the Log4j 2 XML configuration, e.g. like this:
<RollingFile name="RollingFile" fileName="/usr/local/glassfish4.1-webprofile/glassfish/domains/domain1/logs/server.log" filePattern="/usr/local/glassfish4.1-webprofile/glassfish/domains/domain1/logs/server.%d{yyyyMMdd-HH:mm}.log">
<KeepEmptyFilesRolloverStrategy/>
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
<CronTriggeringPolicy schedule="0 * * * * ?"/>
</RollingFile>
The implementation was inspired by this related answer.
On a sidenote, it might be necessary to use the new CronTriggeringPolicy to have empty log files being created at all, as it uses a separate thread. Judging from some other answers on SO, at least some of the other policies cannot react to the trigger as long as the Appender doesn't write out anything.

Grails controller not returning JSON in integration testing

I've got a controller defined:
class FamilyController extends RestfulController {
def springSecurityService
def familyService
static responseFormats = ['json']
FamilyController() {
super(Family)
}
#Override
protected List<Family> listAllResources(Map params) {
def user = springSecurityService.loadCurrentUser()
return Family.findAllByUser(user)
}
#Override
protected Family createResource() {
//def instance = super.createResource()
//TODO: Should be able to run the above line but there is an issue GRAILS-10411 that prevents it.
// Code from parent is below, as soon as the jira is fixed, remove the following lines:
def p1 = params
def p2 = getObjectToBind()
Family instance = resource.newInstance()
bindData instance, getObjectToBind()
/*
* In unit testing, the bindData operation correctly binds the params
* data to instance properties. When running with integration tests,
* this doesn't happen and I have to do the property assignment myself.
* Not sure if this breaks anything elsewhere.
*/
instance.properties = p1
//Code from super ends here
familyService.addFamilyToUser(instance)
return instance
}
#Override
protected Family queryForResource(Serializable id) {
def inst = familyService.safeGetFamily(Long.parseLong(id))
return inst
}
public create()
{
Family r = this.createResource()
render r as JSON
}
public index()
{
render this.listAllResources(params) as JSON
}
}
And for pedantic purposes (I'm new to grails/groovy and I want to watch the controller work), I want to take a look at actual results from the various actions in the controller. So I built an integration test:
#Mock([AdultPlanning, Primary, Secondary, Child, Family, Tenant, User])
class FamilyControllerIntegrationSpec extends IntegrationSpec {
Tenant tenant
User user
FamilyController controller = new FamilyController()
FamilyService familyService = new FamilyService()
def setupSpec() {
tenant = new Tenant(name: 'MMSS')
user = new User(subject: "What is a subject?", identityProvider: "http://google.com/")
tenant.addToUsers(user)
tenant.save()
user.save() // The tenant save should propagate to the user, this is to make sure.
/*
* Set up the custom marshalling code for family objects.
*/
new AdultPlanningMarshaller()
new ChildMarshaller()
new FamilyMarshaller()
new FamilyTypeEnumMarshaller()
return
}
def cleanup() {
}
void "Create a Family"() {
when:
def mSpringSecurityService = mockFor(SpringSecurityService)
/*
* I expect to make n calls (1..3) on loadCurrentUser in this test.
*/
mSpringSecurityService.demand.loadCurrentUser(1..3) { -> return user }
controller.springSecurityService = mSpringSecurityService.createMock()
controller.request.contentType = 'text/json'
controller.index()
def json
try {
json = controller.response.json
}
catch (e)
{
json = null // action didn't emit any json
}
def text = controller.response.text
then:
!json
text == "[]"
when:
controller.params.name = "Test"
controller.params.typeOfFamily = FamilyTypeEnum.SINGLE
controller.familyService.springSecurityService = controller.springSecurityService //mSpringSecurityService.createMock()
/*
* I've tried both GET (default) and POST to get bindData to pick up parameters properly,
* with no joy.
*/
// controller.request.method = "POST"
controller.request.contentType = 'text/json'
controller.create()
try {
json = controller.response.json
}
catch (e)
{
json = null // action didn't emit any json
}
text = controller.response.text
then:
1 == 1
when:
controller.index()
json = controller.response.json
then:
json.name == "Test"
}
}
So in the first when block, I get no families listed (and empty json object and a string of [] in the response), which I expected, but until I actually implemented the index action in code, I got no json back from the action which I didn't expect.
On to the 2nd when. I create a family (problems there with dataBinding but I tossed in a workaround, first things first) and the family gets created and saved, but nothing comes back in the response. I've used the debugger and I see the JSON marshaller triggering for the newly created family so SOMETHING is rendering JSON but where is it going?.
So I'm clearly doing something wrong (or I don't understand what actions are supposed to do in terms of what they return for passing on to the web client) but I'm new in this domain and don't have a clue as to what was done wrong.

Load testing with Cucumber and Java

I need to perform load testing for my REST web service using Cucumber and Java. This REST web service accepts one input which is a String called id and returns complex JSON object.
I wrote a .feature file with Given, When and Then annotations which are defined in java.
The skeleton definition of the class and annotations are here under.
1) Feature (UserActivity.feature)
#functional #integration
Feature: User System Load Test
Scenario Outline: Load test for user data summary from third party UserSystem
Given Simultaneously multiple users are hitting XYZ services with an id=<ids>
When I invoke third party link with above id for multiple users simultaneously
Then I should get response code and response message for all users
Examples:
| ids |
| "pABC123rmqst" |
| "fakXYZ321rmv" |
| "bncMG4218jst" |
2) LoadTestStepDef.java (Feature definition)
package com.system.test.cucumber.steps;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.runners.model.InitializationError;
import com.system.test.restassured.LoadTestUtil;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;
public class LoadTestStepDef
{
private static Logger LOG = LogManager.getLogger( LoadTestStepDef.class );
private String id = null;
private LoadTestUtil service = null;
#Given("^Simultaneously multiple users are hitting XYZ services with an a id=\"(.*?)\"$" )
public void Simultaneously_multiple_users_are_hitting_XYZ_services_with_a_id( String id )
{
LOG.debug( "ID {}", id );
LOG.info( "ID {}", id );
this.id = id;
}
#When( "^I invoke third party link with above id for multiple users simultaneously$" )
public void invoke_third_party_link_With_Above_ID_for_multiple_users_simultaneously() throws InitializationError
{
LOG.debug( " *** Calling simulatenously {} ", id );
LOG.info( " *** Calling simulatenously {}", id );
//Create object of service
service = new LoadTestUtil();
//Set the id to the created service and invoke method
service.setData(id);
service.invokeSimultaneosCalls(10);
}
#Then( "^I should get response code and response message for all users$" )
public void Should_get_response_code_and_response_message_for_all_users()
{
LOG.info( "*** Assert for response Code" );
service.assertHeaderResponseCodeAndMessage();
}
}
3) LoadTestUtil.java
package com.system.test.restassured;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.util.concurrent.Callable;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.jayway.restassured.path.json.JsonPath;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
public class LoadTestUtil
{
private String id = null;
private int numberofTimes;
//Create List to hold all Future<Long>
private List<JsonPath> jsonResponseList = new ArrayList<JsonPath>();
//No arg Constructor
public LoadTestUtil()
{
}
//Set data method to set the initial id
public void setData(String id)
{
LOG.info( "LoadTestUtil.setData()", id );
this.id = id;
}
//This method is used call the REST webservice N times using threads and get response
public void invokeSimultaneosCalls(int numberofTimes)
{
LOG.info( "LoadTestUtil.invokeSimultaneosCalls() - Start" );
this.numberofTimes = numberofTimes;
try
{
long start = System.nanoTime();
int numberOfThreads = Runtime.getRuntime().availableProcessors();
LOG.info("Number of processor available {}" , numberOfThreads);
//Create pool for the Executor Service with numberOfThreads.
ExecutorService executor = Executors.newFixedThreadPool(numberOfThreads);
//Create a list to hold the Future object associated with Callable
List<Future<JsonPath>> futureList = new ArrayList<Future<JsonPath>>();
//Create new RESTServiceCallTask instance
Callable<JsonPath> callable = new RESTServiceCallTask(id);
Future<JsonPath> future = null;
//Iterate N number of times to submit the callable object
for(int count=1; count<=numberofTimes;count++)
{
//Submit Callable tasks to the executor
future = executor.submit(callable);
//Add Future to the list to get return value using Future
futureList.add(future);
}
//Create a flag to monitor the thread status. Check whether all worker threads are completed or not
boolean threadStatus = true;
while (threadStatus)
{
if (future.isDone())
{
threadStatus = false;
//Iterate the response obtained from the futureList
for(Future<JsonPath> futuree : futureList)
{
try
{
//print the return value of Future, notice the output delay in console
// because Future.get() waits for task to get completed
JsonPath response = futuree.get();
jsonResponseList.add(response);
}
catch(InterruptedException ie)
{
ie.printStackTrace();
}
catch(ExecutionException ee)
{
ee.printStackTrace();
}
catch(Exception e)
{
e.printStackTrace();
}
}//End of for to iterate the futuree list
} //End of future.isDone()
} //End of while (threadStatus)
//shut down the executor service now
executor.shutdown();
//Calculate the time taken by the threads for execution
executor.awaitTermination(1, TimeUnit.HOURS); // or longer.
long time = System.nanoTime() - start;
logger.info("Tasks took " + time/1e6 + " ms to run");
long milliSeconds = time / 1000000;
long seconds, minutes, hours;
seconds = milliSeconds / 1000;
hours = seconds / 3600;
seconds = seconds % 3600;
seconds = seconds / 60;
minutes = seconds % 60;
logger.info("Task took " + hours + " hours, " + minutes + " minutes and " + seconds + " seconds to complete");
} //End of try block
catch (Exception e)
{
e.printStackTrace();
}
LOG.info("LoadTestUtil.invokeSimultaneosCalls() - jsonResponseList {} " , jsonResponseList);
System.out.println("LoadTestUtil.invokeSimultaneosCalls() - jsonResponseList {} " + jsonResponseList);
LOG.info( "*** LoadTestUtil.invokeSimultaneosCalls() - End" );
}
public void assertHeaderResponseCodeAndMessage(){
//Number of response objects available
int size = jsonResponseList.size();
LOG.info("Number of REST service calls made = ", size);
for(JsonPath jsonResponse : jsonResponseList)
{
String responseCode = jsonResponse.get( "header.response_code").toString();
String responseMessage = jsonResponse.get( "header.response_message").toString();
assertEquals( "200", responseCode);
assertEquals( "success", responseMessage);
}
}
}
4) RESTServiceCallTask.java
This class implements Callable and override the call() method.
In the call() method, the response in the form of JsonPath is returned for each call
package com.system.test.restassured;
import static com.jayway.restassured.RestAssured.basePath;
import static com.jayway.restassured.RestAssured.baseURI;
import static com.jayway.restassured.RestAssured.given;
import static com.jayway.restassured.RestAssured.port;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.system.test.restassured.TestUtil;
import com.jayway.restassured.path.json.JsonPath;
import com.jayway.restassured.response.Response;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
public class RESTServiceCallTask implements Callable<JsonPath>
{
private static Logger LOG = LogManager.getLogger(RESTServiceCallTask.class);
private Response response = null;
private String id;
private String environment;
//private JsonPath jsonPath;
/**
* Constructor initializes the call to third party system
*
* #param id
*/
public RESTServiceCallTask(String id)
{
LOG.info("In RESTServiceCallTask() constructor ");
this.id = id;
//Read the environment variable ENV to get the corresponding environment's REST URL to call
this.environment = System.getProperty("ENV");
baseURI = TestUtil.getbaseURL(environment);
basePath = "/bluelink/tracker/member_summary";
port = 80;
LOG.info(" *** Environment : {}, URI: {} and Resource {} ", environment, baseURI, basePath);
}
//This method is called by the threads to fire the REST service and returns JSONPath for each execution
#Override
public JsonPath call() throws Exception
{
LOG.info(" *** In call() method ");
try
{
response = given().headers("id", this.id).log().all().get();
} catch (Exception e)
{
LOG.error("System Internal Server Error", e);
}
String strResponse = this.response.asString();
LOG.info("Response : {}", strResponse);
JsonPath jsonResponse = new JsonPath(strResponse);
return jsonResponse;
}
}
5) TestUtil.java
This utility class is used to get the REST URL corresponding to the passed environment
package com.system.test.restassured;
import java.util.HashMap;
import java.util.Map;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class TestUtil
{
private static Logger LOG = LogManager.getLogger(TestUtil.class);
private static final Map<String, String> ENVIRONMENT_MAP = new HashMap<String, String>();
static
{
ENVIRONMENT_MAP.put("LOCAL", "http://localhost:9080");
ENVIRONMENT_MAP.put("ENV1", "http://localhost:9080");
ENVIRONMENT_MAP.put("ENV2", "http://localhost:9080");
ENVIRONMENT_MAP.put("ENV3", "http://localhost:9080");
}
public static String getbaseURL(String environment)
{
LOG.info("Environment value fetched = {}", environment);
return ENVIRONMENT_MAP.get(environment);
}
}
The problem here is that the multi-threading feature is not getting executed.
I used the MavenSurefire Plugin and tried with parallel classes and methods. In those cases also the above scenario doesn't work.
Does Cucumber support java multi-threading? If so what is wrong with the above feature definition?
Note - The same task is performed with stand alone program and able to run for 10,000 times
using 4 threads without any issues. However not able to run the above code for 2000 times using Maven. With 2000 times, the system crashed abruptly.
I am using Rational Application Developer 8.5, Websphere Server 8.0 with Maven 3.x for the above setup.
Thanks for your response.

Deserializing from JSON back to joda DateTime in Play 2.0

I can't figure out the magic words to allow posting JSON for a DateTime field in my app. When queried, DateTimes are returned as microseconds since the epoch. When I try to post in that format though ({"started":"1341006642000","task":{"id":1}}), I get "Invalid value: started".
I also tried adding #play.data.format.Formats.DateTime(pattern="yyyy-MM-dd HH:mm:ss") to the started field and posting {"started":"2012-07-02 09:24:45","task":{"id":1}} which had the same result.
The controller method is:
#BodyParser.Of(play.mvc.BodyParser.Json.class)
public static Result create(Long task_id) {
Form<Run> runForm = form(Run.class).bindFromRequest();
for (String key : runForm.data().keySet()) {
System.err.println(key + " => " + runForm.apply(key).value() + "\n");
}
if (runForm.hasErrors())
return badRequest(runForm.errorsAsJson());
Run run = runForm.get();
run.task = Task.find.byId(task_id);
run.save();
ObjectNode result = Json.newObject();
result.put("id", run.id);
return ok(result);
}
I can also see from the output that the values are being received correctly. Anyone know how to make this work?
After reading the "Register a custom DataBinder" section of the Handling form submission page along with the Application global settings page and comparing with this question I came up with the following solution:
I created a custom annotation with an optional format attribute:
package models;
import java.lang.annotation.*;
#Target({ ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
#play.data.Form.Display(name = "format.joda.datetime", attributes = { "format" })
public #interface JodaDateTime {
String format() default "";
}
and registered a custom formatter from onStart:
import java.text.ParseException;
import java.util.Locale;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import play.*;
import play.data.format.Formatters;
public class Global extends GlobalSettings {
#Override
public void onStart(Application app) {
Formatters.register(DateTime.class, new Formatters.AnnotationFormatter<models.JodaDateTime,DateTime>() {
#Override
public DateTime parse(models.JodaDateTime annotation, String input, Locale locale) throws ParseException {
if (input == null || input.trim().isEmpty())
return null;
if (annotation.format().isEmpty())
return new DateTime(Long.parseLong(input));
else
return DateTimeFormat.forPattern(annotation.format()).withLocale(locale).parseDateTime(input);
}
#Override
public String print(models.JodaDateTime annotation, DateTime time, Locale locale) {
if (time == null)
return null;
if (annotation.format().isEmpty())
return time.getMillis() + "";
else
return time.toString(annotation.format(), locale);
}
});
}
}
You can specify a format if you want, or it will use milliseconds since the epoch by default. I was hoping there would be a simpler way since Joda is included with the Play distribution, but this got things working.
Note: you'll need to restart your Play app as it doesn't seem to detect changes to the Global class.