/*
* Copyright (c) 2010-2015 Evolveum
*
* 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 com.evolveum.midpoint.repo.sql;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.delta.ItemDelta;
import com.evolveum.midpoint.prism.delta.builder.DeltaBuilder;
import com.evolveum.midpoint.prism.polystring.PolyString;
import com.evolveum.midpoint.prism.util.PrismAsserts;
import com.evolveum.midpoint.prism.xml.XmlTypeConverter;
import com.evolveum.midpoint.repo.api.RepoAddOptions;
import com.evolveum.midpoint.repo.api.RepoModifyOptions;
import com.evolveum.midpoint.schema.GetOperationOptions;
import com.evolveum.midpoint.schema.SelectorOptions;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.util.exception.ObjectAlreadyExistsException;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.LookupTableRowType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.LookupTableType;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.testng.AssertJUnit;
import org.testng.annotations.Test;
import javax.xml.datatype.XMLGregorianCalendar;
import java.io.File;
import java.io.IOException;
import java.util.*;
import static com.evolveum.midpoint.schema.RetrieveOption.INCLUDE;
import static com.evolveum.midpoint.xml.ns._public.common.common_3.LookupTableRowType.*;
import static com.evolveum.midpoint.xml.ns._public.common.common_3.LookupTableType.F_ROW;
import static com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType.F_NAME;
import static org.testng.AssertJUnit.assertNotNull;
import static org.testng.AssertJUnit.assertTrue;
/**
* @author mederly
*/
@ContextConfiguration(locations = {"../../../../../ctx-test.xml"})
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
public class LookupTableTest extends BaseSQLRepoTest {
private static final Trace LOGGER = TraceManager.getTrace(LookupTableTest.class);
private static final File TEST_DIR = new File("src/test/resources/lookup");
private static final long TIMESTAMP_TOLERANCE = 10000L;
private String tableOid;
protected RepoModifyOptions getModifyOptions() {
return null;
}
@Test
public void test100AddTableNonOverwrite() throws Exception {
PrismObject<LookupTableType> table = prismContext.parseObject(new File(TEST_DIR, "table-0.xml"));
OperationResult result = new OperationResult("test100AddTableNonOverwrite");
tableOid = repositoryService.addObject(table, null, result);
result.recomputeStatus();
assertTrue(result.isSuccess());
// rereading
PrismObject<LookupTableType> expected = prismContext.parseObject(new File(TEST_DIR, "table-0.xml"));
checkTable(tableOid, expected, result);
}
@Test(expectedExceptions = ObjectAlreadyExistsException.class)
public void test105AddTableNonOverwriteExisting() throws Exception {
PrismObject<LookupTableType> table = prismContext.parseObject(new File(TEST_DIR, "table-0.xml"));
OperationResult result = new OperationResult("test105AddTableNonOverwriteExisting");
repositoryService.addObject(table, null, result);
}
@Test
public void test108AddTableOverwriteExisting() throws Exception {
PrismObject<LookupTableType> table = prismContext.parseObject(new File(TEST_DIR, "table-1.xml"));
OperationResult result = new OperationResult("test108AddTableOverwriteExisting");
table.setOid(tableOid); // doesn't work without specifying OID
tableOid = repositoryService.addObject(table, RepoAddOptions.createOverwrite(), result);
// rereading, as repo strips cases from the campaign (!)
PrismObject<LookupTableType> expected = prismContext.parseObject(new File(TEST_DIR, "table-1.xml"));
checkTable(tableOid, expected, result);
}
@Test
public void test200ModifyTableProperties() throws Exception {
OperationResult result = new OperationResult("test200ModifyTableProperties");
List<ItemDelta<?,?>> modifications = DeltaBuilder.deltaFor(LookupTableType.class, prismContext)
.item(F_NAME).replace(new PolyString("Table 1", "table 1"))
.asItemDeltas();
executeAndCheckModification(modifications, result, 1, null);
}
@Test
public void test210ModifyRowProperties() throws Exception {
OperationResult result = new OperationResult("test210ModifyRowProperties");
List<ItemDelta<?,?>> modifications = DeltaBuilder.deltaFor(LookupTableType.class, prismContext)
.item(F_ROW, 1, F_KEY).replace("key 1")
.item(F_ROW, 2, F_VALUE).replace()
.item(F_ROW, 3, F_LABEL).replace(new PolyString("label 3"))
.item(F_ROW, 3, F_LAST_CHANGE_TIMESTAMP)
.replace(XmlTypeConverter.createXMLGregorianCalendar(new Date(99, 10, 10)))
.asItemDeltas();
executeAndCheckModification(modifications, result, 0, Arrays.asList("key 1", "2 key"));
}
@Test
public void test220AddRemoveValues() throws Exception {
OperationResult result = new OperationResult("test220AddRemoveValues");
List<ItemDelta<?,?>> modifications = DeltaBuilder.deltaFor(LookupTableType.class, prismContext)
.item(F_ROW, 1, F_VALUE).delete("first value")
.item(F_ROW, 2, F_VALUE).add("value 2")
.asItemDeltas();
executeAndCheckModification(modifications, result, 0, Arrays.asList("key 1", "2 key"));
}
@Test(expectedExceptions = SchemaException.class)
public void test222ReplaceKeyToNull() throws Exception {
OperationResult result = new OperationResult("test222ReplaceKeyToNull");
List<ItemDelta<?,?>> modifications = DeltaBuilder.deltaFor(LookupTableType.class, prismContext)
.item(F_ROW, 1, F_KEY).replace()
.asItemDeltas();
repositoryService.modifyObject(LookupTableType.class, tableOid, modifications, null, result);
}
@Test(expectedExceptions = SchemaException.class)
public void test224DeleteKeyValue() throws Exception {
OperationResult result = new OperationResult("test224DeleteKeyValue");
List<ItemDelta<?,?>> modifications = DeltaBuilder.deltaFor(LookupTableType.class, prismContext)
.item(F_ROW, 1, F_KEY).delete("key 1")
.asItemDeltas();
repositoryService.modifyObject(LookupTableType.class, tableOid, modifications, null, result);
}
@Test(expectedExceptions = SchemaException.class)
public void test226AddKeylessRow() throws Exception {
OperationResult result = new OperationResult("test226AddKeylessRow");
List<ItemDelta<?,?>> modifications = DeltaBuilder.deltaFor(LookupTableType.class, prismContext)
.item(F_ROW).add(new LookupTableRowType())
.asItemDeltas();
repositoryService.modifyObject(LookupTableType.class, tableOid, modifications, null, result);
}
@Test(expectedExceptions = SchemaException.class)
public void test228AddKeylessRow2() throws Exception {
OperationResult result = new OperationResult("test228AddKeylessRow2");
LookupTableRowType row = new LookupTableRowType();
row.setValue("value");
List<ItemDelta<?,?>> modifications = DeltaBuilder.deltaFor(LookupTableType.class, prismContext)
.item(F_ROW).add(row)
.asItemDeltas();
repositoryService.modifyObject(LookupTableType.class, tableOid, modifications, null, result);
}
@Test
public void test230ModifyTableAndRow() throws Exception {
OperationResult result = new OperationResult("test230ModifyTableAndRow");
List<ItemDelta<?,?>> modifications = DeltaBuilder.deltaFor(LookupTableType.class, prismContext)
.item(F_NAME).replace(new PolyString("Table 111", "table 111"))
.item(F_ROW, 2, F_KEY).replace("key 2")
.asItemDeltas();
executeAndCheckModification(modifications, result, 1, Arrays.asList("key 2"));
}
@Test
public void test240AddRows() throws Exception {
OperationResult result = new OperationResult("test240AddRows");
LookupTableRowType rowNoId = new LookupTableRowType(prismContext);
rowNoId.setKey("key new");
rowNoId.setValue("value new");
rowNoId.setLastChangeTimestamp(XmlTypeConverter.createXMLGregorianCalendar(new Date(99, 3, 4)));
LookupTableRowType rowNoId2 = new LookupTableRowType(prismContext);
rowNoId2.setKey("key new 2");
rowNoId2.setValue("value new 2");
LookupTableRowType row4 = new LookupTableRowType(prismContext);
row4.setId(4L);
row4.setKey("key 4");
row4.setValue("value 4");
List<ItemDelta<?,?>> modifications = DeltaBuilder.deltaFor(LookupTableType.class, prismContext)
.item(F_ROW).add(rowNoId, rowNoId2, row4)
.asItemDeltas();
executeAndCheckModification(modifications, result, 0, keysOf(rowNoId2, row4));
// beware, ID for row4 was re-generated -- using client-provided IDs is not recommended anyway
}
@Test
public void test245AddDuplicateRows() throws Exception {
OperationResult result = new OperationResult("test245AddDuplicateRows");
LookupTableRowType rowNoId = new LookupTableRowType(prismContext);
rowNoId.setKey("key new");
rowNoId.setValue("value new NEW");
LookupTableRowType row4 = new LookupTableRowType(prismContext);
row4.setId(4L);
row4.setKey("key 4");
row4.setValue("value 4 NEW");
List<ItemDelta<?,?>> modifications = DeltaBuilder.deltaFor(LookupTableType.class, prismContext)
.item(F_ROW).add(rowNoId, row4)
.asItemDeltas();
executeAndCheckModification(modifications, result, 0, keysOf(rowNoId, row4), keysOf(rowNoId, row4));
// beware, ID for row4 was re-generated -- using client-provided IDs is not recommended anyway
}
@Test
public void test250DeleteRow() throws Exception {
OperationResult result = new OperationResult("test250DeleteRow");
LookupTableRowType row3 = new LookupTableRowType(prismContext);
row3.setId(3L);
List<ItemDelta<?,?>> modifications = DeltaBuilder.deltaFor(LookupTableType.class, prismContext)
.item(F_ROW).delete(row3)
.asItemDeltas();
executeAndCheckModification(modifications, result, 0, null);
}
@Test
public void test252DeleteNonexistingRow() throws Exception {
OperationResult result = new OperationResult("test252DeleteNonexistingRow");
LookupTableRowType rowNoId = new LookupTableRowType(prismContext);
rowNoId.setKey("non-existing-key");
List<ItemDelta<?,?>> modifications = DeltaBuilder.deltaFor(LookupTableType.class, prismContext)
.item(F_ROW).delete(rowNoId)
.asItemDeltas();
executeAndCheckModification(modifications, result, 0, null);
}
private List<String> keysOf(LookupTableRowType... rows) {
List<String> keys = new ArrayList<>(rows.length);
for (LookupTableRowType row : rows) {
keys.add(row.getKey());
}
return keys;
}
@Test
public void test255AddDeleteRows() throws Exception {
OperationResult result = new OperationResult("test255AddDeleteRows");
LookupTableRowType rowNoId = new LookupTableRowType(prismContext);
rowNoId.setKey("key new new");
rowNoId.setValue("value new new");
rowNoId.setLastChangeTimestamp(XmlTypeConverter.createXMLGregorianCalendar(new Date(99, 3, 4)));
LookupTableRowType row5 = new LookupTableRowType(prismContext);
row5.setKey("key 5");
row5.setValue("value 5");
LookupTableRowType row4 = new LookupTableRowType(prismContext);
row4.setId(4L);
List<ItemDelta<?,?>> modifications = DeltaBuilder.deltaFor(LookupTableType.class, prismContext)
.item(F_ROW).add(rowNoId, row5).delete(row4)
.asItemDeltas();
executeAndCheckModification(modifications, result, 0, keysOf(row5));
}
@Test
public void test260ReplaceRowsExistingId() throws Exception {
OperationResult result = new OperationResult("test260ReplaceRowsExistingId");
LookupTableRowType row5 = new LookupTableRowType(prismContext);
row5.setId(5L); // dangerous
row5.setKey("key 5 plus");
row5.setValue("value 5 plus");
row5.setLastChangeTimestamp(XmlTypeConverter.createXMLGregorianCalendar(new Date(99, 3, 10)));
List<ItemDelta<?,?>> modifications = DeltaBuilder.deltaFor(LookupTableType.class, prismContext)
.item(F_ROW).replace(row5)
.asItemDeltas();
executeAndCheckModification(modifications, result, 0, null);
}
@Test
public void test265ReplaceRowsNewId() throws Exception {
OperationResult result = new OperationResult("test265ReplaceRowsNewId");
LookupTableRowType rowNoId = new LookupTableRowType(prismContext);
rowNoId.setKey("key new plus");
rowNoId.setValue("value now plus");
List<ItemDelta<?,?>> modifications = DeltaBuilder.deltaFor(LookupTableType.class, prismContext)
.item(F_ROW).replace(rowNoId)
.asItemDeltas();
executeAndCheckModification(modifications, result, 0, keysOf(rowNoId));
}
@Test
public void test900DeleteTable() throws Exception {
OperationResult result = new OperationResult("test900DeleteTable");
repositoryService.deleteObject(LookupTableType.class, tableOid, result);
result.recomputeStatus();
assertTrue(result.isSuccess());
}
private void checkTable(String tableOid, PrismObject<LookupTableType> expectedObject, OperationResult result) throws SchemaException, ObjectNotFoundException {
SelectorOptions<GetOperationOptions> retrieve = SelectorOptions.create(F_ROW, GetOperationOptions.createRetrieve(INCLUDE));
PrismObject<LookupTableType> table = repositoryService.getObject(LookupTableType.class, tableOid, Arrays.asList(retrieve), result);
expectedObject.setOid(tableOid);
PrismAsserts.assertEquivalent("Table is not as expected", expectedObject, table);
}
protected void executeAndCheckModification(List<ItemDelta<?,?>> modifications, OperationResult result, int versionDelta,
List<String> keysWithGeneratedTimestamps)
throws ObjectNotFoundException, SchemaException, ObjectAlreadyExistsException, IOException {
executeAndCheckModification(modifications, result, versionDelta, keysWithGeneratedTimestamps, null);
}
protected void executeAndCheckModification(List<ItemDelta<?,?>> modifications, OperationResult result, int versionDelta,
List<String> keysWithGeneratedTimestamps, List<String> replacedKeys)
throws ObjectNotFoundException, SchemaException, ObjectAlreadyExistsException, IOException {
RepoModifyOptions modifyOptions = getModifyOptions();
if (RepoModifyOptions.isExecuteIfNoChanges(modifyOptions) && versionDelta == 0) {
versionDelta = 1;
}
PrismObject<LookupTableType> before = getFullTable(tableOid, result);
repositoryService.modifyObject(LookupTableType.class, tableOid, modifications, modifyOptions, result);
checkTable(tableOid, result, before, modifications, Integer.parseInt(before.getVersion()) + versionDelta,
keysWithGeneratedTimestamps, replacedKeys);
}
private void checkTable(String oid, OperationResult result, PrismObject<LookupTableType> expectedObject,
List<ItemDelta<?, ?>> modifications, int expectedVersion,
List<String> keysWithNewGeneratedTimestamps, List<String> replacedKeys)
throws SchemaException, ObjectNotFoundException, IOException {
expectedObject.setOid(oid);
// remove keys that will be replaced
if (replacedKeys != null) {
Iterator<LookupTableRowType> iterator = expectedObject.asObjectable().getRow().iterator();
while (iterator.hasNext()) {
if (replacedKeys.contains(iterator.next().getKey())) {
iterator.remove();
}
}
}
if (modifications != null) {
ItemDelta.applyTo(modifications, expectedObject);
}
LOGGER.trace("Expected object = \n{}", expectedObject.debugDump());
PrismObject<LookupTableType> actualObject = getFullTable(oid, result);
LOGGER.trace("Actual object from repo = \n{}", actualObject.debugDump());
// before comparison, check and remove generated timestamps
if (keysWithNewGeneratedTimestamps != null) {
for (String key : keysWithNewGeneratedTimestamps) {
LookupTableRowType row = findRow(actualObject, key, true);
checkCurrentTimestamp(row);
row.setLastChangeTimestamp(null);
LookupTableRowType rowExp = findRow(expectedObject, key, false);
if (rowExp != null) {
rowExp.setLastChangeTimestamp(null);
}
}
}
PrismAsserts.assertEquivalent("Table is not as expected", expectedObject, actualObject);
AssertJUnit.assertEquals("Incorrect version", expectedVersion, Integer.parseInt(actualObject.getVersion()));
}
private void checkCurrentTimestamp(LookupTableRowType row) {
XMLGregorianCalendar ts = row.getLastChangeTimestamp();
assertNotNull("No last change timestamp in " + row, ts);
long diff = System.currentTimeMillis() - XmlTypeConverter.toMillis(ts);
assertTrue("Last change timestamp in " + row + " is too old or too new; diff = " + diff, diff >= 0 && diff <= TIMESTAMP_TOLERANCE);
}
private LookupTableRowType findRow(PrismObject<LookupTableType> table, String key, boolean mustBePresent) {
for (LookupTableRowType row : table.asObjectable().getRow()) {
if (key.equals(row.getKey())) {
return row;
}
}
if (mustBePresent) {
throw new IllegalStateException("No row with key " + key + " in " + table);
} else {
return null;
}
}
private PrismObject<LookupTableType> getFullTable(String oid, OperationResult result) throws ObjectNotFoundException, SchemaException {
SelectorOptions<GetOperationOptions> retrieve = SelectorOptions.create(F_ROW, GetOperationOptions.createRetrieve(INCLUDE));
return repositoryService.getObject(LookupTableType.class, oid, Arrays.asList(retrieve), result);
}
}