/*
* ModeShape (http://www.modeshape.org)
*
* 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.modeshape.jcr;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsNull.notNullValue;
import static org.junit.Assert.assertThat;
import java.net.URL;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import javax.jcr.Session;
import org.infinispan.schematic.document.Changes;
import org.infinispan.schematic.document.Document;
import org.infinispan.schematic.document.EditableDocument;
import org.infinispan.schematic.document.Editor;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.modeshape.common.FixFor;
import org.modeshape.jcr.ClientLoad.Client;
import org.modeshape.jcr.ClientLoad.ClientResultProcessor;
import org.modeshape.jcr.ModeShapeEngine.State;
import org.modeshape.jcr.RepositoryConfiguration.Default;
import org.modeshape.jcr.RepositoryConfiguration.FieldName;
public class ModeShapeEngineTest {
private RepositoryConfiguration config;
private ModeShapeEngine engine;
@Before
public void beforeEach() throws Exception {
config = RepositoryConfiguration.read("{ \"name\":\"my-repo\" }");
engine = new ModeShapeEngine();
}
@After
public void afterEach() throws Exception {
try {
engine.shutdown();
} finally {
engine = null;
config = null;
}
}
@Test
public void shouldStart() {
engine.start();
assertThat(engine.getState(), is(State.RUNNING));
}
@Test
public void shouldAllowStartToBeCalledMultipleTimes() {
for (int i = 0; i != 10; ++i) {
engine.start();
assertThat(engine.getState(), is(State.RUNNING));
}
}
@Test
public void shouldAllowShutdownToBeCalledEvenIfNotRunning() {
assertThat(engine.getState(), is(State.NOT_RUNNING));
for (int i = 0; i != 10; ++i) {
engine.shutdown();
assertThat(engine.getState(), is(State.NOT_RUNNING));
}
}
@Test
public void shouldStartThenStopThenRestart() throws Exception {
assertThat(engine.getState(), is(State.NOT_RUNNING));
engine.start();
assertThat(engine.getState(), is(State.RUNNING));
for (int i = 0; i != 5; ++i) {
engine.shutdown().get(3L, TimeUnit.SECONDS);
assertThat(engine.getState(), is(State.NOT_RUNNING));
}
engine.start();
assertThat(engine.getState(), is(State.RUNNING));
}
@Test
public void shouldDeployRepositoryConfiguration() throws Exception {
engine.start();
JcrRepository repository = engine.deploy(config);
assertThat(repository, is(notNullValue()));
assertThat(repository, is(notNullValue()));
}
@Test( expected = ConfigurationException.class )
public void shouldFailToDeployRepositoryConfigurationWithoutName() throws Throwable {
config = new RepositoryConfiguration(); // without a name!
assertThat(config.validate().hasErrors(), is(true));
engine.start();
engine.deploy(config);
}
@Test
public void shouldNotAutomaticallyStartDeployedRepositories() throws Exception {
engine.start();
JcrRepository repository = engine.deploy(config);
String name = repository.getName();
assertThat(engine.getRepositoryState(name), is(State.NOT_RUNNING));
engine.startRepository(name).get(); // blocks
assertThat(engine.getRepositoryState(name), is(State.RUNNING));
engine.shutdownRepository(name).get(); // blocks
assertThat(engine.getRepositoryState(name), is(State.NOT_RUNNING));
}
@Test
public void shouldAutomaticallyStartRepositoryUponLogin() throws Exception {
engine.start();
JcrRepository repository = engine.deploy(config);
String name = repository.getName();
assertThat(engine.getRepositoryState(name), is(State.NOT_RUNNING));
assertThat(repository.getState(), is(State.NOT_RUNNING));
for (int i = 0; i != 4; ++i) {
javax.jcr.Session session = repository.login();
assertThat(repository.getState(), is(State.RUNNING));
session.logout();
}
assertThat(engine.getRepositoryState(name), is(State.RUNNING));
engine.shutdownRepository(name).get(); // blocks
assertThat(engine.getRepositoryState(name), is(State.NOT_RUNNING));
}
@Test
public void shouldAllowConcurrentLoginWhileRequiringAutoStartOfRepository() throws Exception {
engine.start();
final JcrRepository repository = engine.deploy(config);
String name = repository.getName();
assertThat(engine.getRepositoryState(name), is(State.NOT_RUNNING));
assertThat(repository.getState(), is(State.NOT_RUNNING));
List<Client<Session>> results = ClientLoad.runSimultaneously(10, new Callable<Session>() {
@Override
public Session call() throws Exception {
return repository.login();
}
});
// NOTE THAT MUCH OF THE TIME DELAY SEEN WITHIN THE CLIENT TIMES IS DUE TO THE EXECUTOR SERVICE AND THREAD SWITCHING.
// We're only checking times here to make sure that they don't quit after failing to reach the barrier.
final boolean print = false;
ClientLoad.forEachResult(results, new ClientResultProcessor<Session>() {
@Override
public void process( Client<Session> clientResult ) throws Exception {
assertThat(clientResult.isSuccess(), is(true));
assertThat(clientResult.getTime(TimeUnit.SECONDS) < 20, is(true));
clientResult.getResult().logout();
if (print) System.out.println("Client result: " + clientResult.getTime(TimeUnit.MILLISECONDS) + " ms");
}
});
long start = System.nanoTime();
Session session = repository.login();
if (print) System.out.println("Session result: "
+ TimeUnit.MILLISECONDS.convert(System.nanoTime() - start, TimeUnit.NANOSECONDS) + " ms");
session.logout();
// Make sure the repository is running ...
assertThat(engine.getRepositoryState(name), is(State.RUNNING));
// Shutdown repository ...
engine.shutdownRepository(name).get(); // blocks
assertThat(engine.getRepositoryState(name), is(State.NOT_RUNNING));
}
@Test
public void shouldAllowConcurrentLoginOfAlreadyStartedRepository() throws Exception {
engine.start();
final JcrRepository repository = engine.deploy(config);
String name = repository.getName();
assertThat(engine.getRepositoryState(name), is(State.NOT_RUNNING));
assertThat(repository.getState(), is(State.NOT_RUNNING));
engine.startRepository(name).get(); // blocks
assertThat(repository.getState(), is(State.RUNNING));
// NOTE THAT MUCH OF THE TIME DELAY SEEN WITHIN THE CLIENT TIMES IS DUE TO THE EXECUTOR SERVICE AND THREAD SWITCHING.
// We're only checking times here to make sure that they don't quit after failing to reach the barrier.
// Plus, note that creating sessions within this thread happens very fast (usually < 1ms), whether its
// the initial priming session, or the one created before all the client's sessions are terminated,
// or the one created after all the others were closed.
final boolean print = false;
// Prime the sessions ...
long start = System.nanoTime();
Session session = repository.login();
if (print) System.out.println("Initial session: "
+ TimeUnit.MILLISECONDS.convert(Math.abs(System.nanoTime() - start), TimeUnit.NANOSECONDS)
+ " ms");
session.logout();
// Now create bunch of sessions simultaneously ...
List<Client<Session>> results = ClientLoad.run(20, new Callable<Session>() {
@Override
public Session call() throws Exception {
return repository.login();
}
});
// Create another session (before all the clients' sessions have been closed) ...
start = System.nanoTime();
session = repository.login();
if (print) System.out.println("Before close: "
+ TimeUnit.MILLISECONDS.convert(Math.abs(System.nanoTime() - start), TimeUnit.NANOSECONDS)
+ " ms");
session.logout();
ClientLoad.forEachResult(results, new ClientResultProcessor<Session>() {
@Override
public void process( Client<Session> clientResult ) throws Exception {
assertThat(clientResult.isSuccess(), is(true));
assertThat(clientResult.getTime(TimeUnit.SECONDS) < 20, is(true));
clientResult.getResult().logout();
if (print) System.out.println("Client result: " + clientResult.getTime(TimeUnit.MICROSECONDS) + " ms");
}
});
// Create another session (after all the clients' sessions have been closed) ...
start = System.nanoTime();
session = repository.login();
if (print) System.out.println("After close: "
+ TimeUnit.MILLISECONDS.convert(Math.abs(System.nanoTime() - start), TimeUnit.NANOSECONDS)
+ " ms");
session.logout();
// Make sure the repository is running ...
assertThat(engine.getRepositoryState(name), is(State.RUNNING));
// Shutdown repository ...
engine.shutdownRepository(name).get(); // blocks
assertThat(engine.getRepositoryState(name), is(State.NOT_RUNNING));
}
@Test
public void shouldAllowUpdatingRepositoryConfigurationWhileNotRunning() throws Exception {
engine.start();
JcrRepository repository = engine.deploy(config);
String name = repository.getName();
assertThat(engine.getRepositoryState(name), is(State.NOT_RUNNING));
assertThat(config.getBinaryStorage().getMinimumBinarySizeInBytes(), is(Default.MINIMUM_BINARY_SIZE_IN_BYTES));
// Change the configuration ...
long newLargeValueSizeInBytes = Default.MINIMUM_BINARY_SIZE_IN_BYTES * 2;
Editor editor = repository.getConfiguration().edit();
EditableDocument binaryStorage = editor.getOrCreateDocument(FieldName.STORAGE)
.getOrCreateDocument(FieldName.BINARY_STORAGE);
binaryStorage.setNumber(FieldName.MINIMUM_BINARY_SIZE_IN_BYTES, newLargeValueSizeInBytes);
Changes changes = editor.getChanges();
// Apply the changes to the deployed repository ...
engine.update(name, changes).get(); // blocks
assertThat(engine.getRepositoryState(name), is(State.NOT_RUNNING));
RepositoryConfiguration newConfig = engine.getRepository(name).getConfiguration();
assertThat(newConfig.getBinaryStorage().getMinimumBinarySizeInBytes(), is(newLargeValueSizeInBytes));
}
@Test
public void shouldAllowUpdatingRepositoryConfigurationWhileRunning() throws Exception {
engine.start();
JcrRepository repository = engine.deploy(config);
String name = repository.getName();
assertThat(engine.getRepositoryState(name), is(State.NOT_RUNNING));
engine.startRepository(name).get(); // blocks
assertThat(engine.getRepositoryState(name), is(State.RUNNING));
long defaultLargeValueSize = Default.MINIMUM_BINARY_SIZE_IN_BYTES;
assertThat(config.getBinaryStorage().getMinimumBinarySizeInBytes(), is(defaultLargeValueSize));
assertThat(repository.repositoryCache().largeValueSizeInBytes(), is(defaultLargeValueSize));
// Change the configuration. We'll do something simple, like changing the large value size ...
long newLargeValueSizeInBytes = defaultLargeValueSize * 2L;
Editor editor = repository.getConfiguration().edit();
EditableDocument binaryStorage = editor.getOrCreateDocument(FieldName.STORAGE)
.getOrCreateDocument(FieldName.BINARY_STORAGE);
binaryStorage.setNumber(FieldName.MINIMUM_BINARY_SIZE_IN_BYTES, newLargeValueSizeInBytes);
Changes changes = editor.getChanges();
// Apply the changes to the deployed repository ...
engine.update(name, changes).get(); // blocks
assertThat(engine.getRepositoryState(name), is(State.RUNNING));
// Verify the running repository and its configuraiton are using the new value ...
RepositoryConfiguration newConfig = engine.getRepository(name).getConfiguration();
assertThat(newConfig.getBinaryStorage().getMinimumBinarySizeInBytes(), is(newLargeValueSizeInBytes));
assertThat(repository.repositoryCache().largeValueSizeInBytes(), is(newLargeValueSizeInBytes));
}
@Test
public void shouldAllowUpdatingSequencerInformationWhenRunning() throws Exception {
URL configUrl = getClass().getClassLoader().getResource("config/repo-config.json");
engine.start();
config = RepositoryConfiguration.read(configUrl);
JcrRepository repository = engine.deploy(config);
// Obtain an editor ...
Editor editor = repository.getConfiguration().edit();
EditableDocument sequencing = editor.getDocument(FieldName.SEQUENCING);
EditableDocument sequencers = sequencing.getDocument(FieldName.SEQUENCERS);
EditableDocument sequencerA = sequencers.getDocument("CND sequencer");
// Verify the existing value ...
List<?> exprs = sequencerA.getArray(FieldName.PATH_EXPRESSIONS);
assertThat(exprs.size(), is(1));
assertThat((String)exprs.get(0), is("default://(*.cnd)/jcr:content[@jcr:data]"));
// Set the new value ...
sequencerA.setArray(FieldName.PATH_EXPRESSIONS, "//*.ddl", "//*.xml");
// And apply the changes to the repository's configuration ...
Changes changes = editor.getChanges();
engine.update(config.getName(), changes).get(); // don't forget to wait!
// Verify the configuration was changed successfully ...
RepositoryConfiguration config2 = engine.getRepositoryConfiguration(config.getName());
Document sequencerA2 = (Document)config2.getDocument()
.getDocument(FieldName.SEQUENCING)
.getDocument(FieldName.SEQUENCERS)
.get("CND sequencer");
List<?> exprs2 = sequencerA2.getArray(FieldName.PATH_EXPRESSIONS);
assertThat(exprs2.size(), is(2));
assertThat((String)exprs2.get(0), is("//*.ddl"));
assertThat((String)exprs2.get(1), is("//*.xml"));
}
@FixFor( "MODE-1769" )
@Test
public void shouldAllowUsingAsyncCacheStore() throws Exception {
URL configUrl = getClass().getClassLoader().getResource("config/repo-config-persistent-async-cache.json");
engine.start();
config = RepositoryConfiguration.read(configUrl);
JcrRepository repository = engine.deploy(config);
Session session = null;
try {
session = repository.login();
} finally {
if (session != null) session.logout();
}
}
}