/*! ******************************************************************************
*
* Pentaho Data Integration
*
* Copyright (C) 2002-2016 by Pentaho : http://www.pentaho.com
*
*******************************************************************************
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
package org.pentaho.di.concurrency;
import org.apache.commons.collections.ListUtils;
import org.junit.BeforeClass;
import org.junit.Test;
import org.pentaho.di.job.Job;
import org.pentaho.di.job.JobConfiguration;
import org.pentaho.di.www.CarteObjectEntry;
import org.pentaho.di.www.JobMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class JobMapConcurrencyTest {
public static final String JOB_NAME_STRING = "job";
public static final String JOB_ID_STRING = "job";
public static final int INITIAL_JOB_MAP_SIZE = 100;
private static final int gettersAmount = 20;
private static final int replaceAmount = 20;
private static final int updatersAmount = 5;
private static final int updatersCycles = 10;
private static JobMap jobMap;
@BeforeClass
public static void init() {
jobMap = new JobMap();
for ( int i = 0; i < INITIAL_JOB_MAP_SIZE; i++ ) {
jobMap.addJob( JOB_NAME_STRING + i, JOB_ID_STRING + i, mockJob( i ), mock( JobConfiguration.class ) );
}
}
private static Job mockJob( int id ) {
Job job = mock( Job.class );
when( job.getContainerObjectId() ).thenReturn( JOB_NAME_STRING + id );
return job;
}
@Test
public void updateGetAndReplaceConcurrently() throws Exception {
AtomicBoolean condition = new AtomicBoolean( true );
AtomicInteger generator = new AtomicInteger( 10 );
List<Updater> updaters = new ArrayList<>();
for ( int i = 0; i < updatersAmount; i++ ) {
Updater updater = new Updater( jobMap, generator, updatersCycles );
updaters.add( updater );
}
List<Getter> getters = new ArrayList<>();
for ( int i = 0; i < gettersAmount; i++ ) {
getters.add( new Getter( jobMap, condition ) );
}
List<Replacer> replacers = new ArrayList<>();
for ( int i = 0; i < replaceAmount; i++ ) {
replacers.add( new Replacer( jobMap, condition ) );
}
//noinspection unchecked
ConcurrencyTestRunner.runAndCheckNoExceptionRaised( updaters, ListUtils.union( replacers, getters ), condition );
}
private static class Getter extends StopOnErrorCallable<Object> {
private final JobMap jobMap;
private final Random random;
public Getter( JobMap jobMap, AtomicBoolean condition ) {
super( condition );
this.jobMap = jobMap;
this.random = new Random();
}
@Override
public Object doCall() throws Exception {
while ( condition.get() ) {
int i = random.nextInt( INITIAL_JOB_MAP_SIZE );
CarteObjectEntry entry = jobMap.getJobObjects().get( i );
if ( entry == null ) {
throw new IllegalStateException(
String.format( "Returned CarteObjectEntry must not be null. EntryId = %d", i ) );
}
final String jobName = JOB_NAME_STRING + i;
Job job = jobMap.getJob( entry.getName() );
if ( job == null ) {
throw new IllegalStateException( String.format( "Returned job must not be null. Job name = %s", jobName ) );
}
JobConfiguration jobConfiguration = jobMap.getConfiguration( entry.getName() );
if ( jobConfiguration == null ) {
throw new IllegalStateException(
String.format( "Returned jobConfiguration must not be null. Job name = %s", jobName ) );
}
}
return null;
}
}
private static class Updater implements Callable<Exception> {
private final JobMap jobMap;
private final AtomicInteger generator;
private final int cycles;
public Updater( JobMap jobMap, AtomicInteger generator, int cycles ) {
this.jobMap = jobMap;
this.generator = generator;
this.cycles = cycles;
}
@Override
public Exception call() throws Exception {
Exception exception = null;
try {
for ( int i = 0; i < cycles; i++ ) {
int id = generator.get();
jobMap.addJob( JOB_NAME_STRING + id, JOB_ID_STRING + id, mockJob( id ), mock( JobConfiguration.class ) );
}
} catch ( Exception e ) {
exception = e;
}
return exception;
}
}
private static class Replacer extends StopOnErrorCallable<Object> {
private final JobMap jobMap;
private final Random random;
public Replacer( JobMap jobMap, AtomicBoolean condition ) {
super( condition );
this.jobMap = jobMap;
this.random = new Random();
}
@Override
public Object doCall() throws Exception {
int i = random.nextInt( INITIAL_JOB_MAP_SIZE );
final String jobName = JOB_NAME_STRING + i;
final String jobId = JOB_ID_STRING + i;
CarteObjectEntry entry = new CarteObjectEntry( jobName, jobId );
jobMap.replaceJob( entry, mockJob( i + 1 ), mock( JobConfiguration.class ) );
return null;
}
}
}