/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2013-2014 ForgeRock AS. All Rights Reserved
*
* The contents of this file are subject to the terms
* of the Common Development and Distribution License
* (the License). You may not use this file except in
* compliance with the License.
*
* You can obtain a copy of the License at
* http://forgerock.org/license/CDDLv1.0.html
* See the License for the specific language governing
* permission and limitations under the License.
*
* When distributing Covered Code, include this CDDL
* Header Notice in each file and include the License file
* at http://forgerock.org/license/CDDLv1.0.html
* If applicable, add the following below the CDDL Header,
* with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*/
package org.identityconnectors.testconnector;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.TreeSet;
import org.identityconnectors.framework.common.exceptions.AlreadyExistsException;
import org.identityconnectors.framework.common.exceptions.ConnectorException;
import org.identityconnectors.framework.common.exceptions.InvalidAttributeValueException;
import org.identityconnectors.framework.common.exceptions.PreconditionFailedException;
import org.identityconnectors.framework.common.exceptions.PreconditionRequiredException;
import org.identityconnectors.framework.common.objects.Attribute;
import org.identityconnectors.framework.common.objects.AttributeBuilder;
import org.identityconnectors.framework.common.objects.AttributeUtil;
import org.identityconnectors.framework.common.objects.AttributesAccessor;
import org.identityconnectors.framework.common.objects.ConnectorObject;
import org.identityconnectors.framework.common.objects.ConnectorObjectBuilder;
import org.identityconnectors.framework.common.objects.Name;
import org.identityconnectors.framework.common.objects.ObjectClass;
import org.identityconnectors.framework.common.objects.OperationOptions;
import org.identityconnectors.framework.common.objects.ResultsHandler;
import org.identityconnectors.framework.common.objects.SearchResult;
import org.identityconnectors.framework.common.objects.SortKey;
import org.identityconnectors.framework.common.objects.SyncResultsHandler;
import org.identityconnectors.framework.common.objects.SyncToken;
import org.identityconnectors.framework.common.objects.Uid;
import org.identityconnectors.framework.common.objects.filter.Filter;
import org.identityconnectors.framework.common.objects.filter.FilterTranslator;
import org.identityconnectors.framework.spi.Configuration;
import org.identityconnectors.framework.spi.SearchResultsHandler;
import org.identityconnectors.framework.spi.SyncTokenResultsHandler;
import org.identityconnectors.framework.spi.operations.CreateOp;
import org.identityconnectors.framework.spi.operations.DeleteOp;
import org.identityconnectors.framework.spi.operations.SearchOp;
import org.identityconnectors.framework.spi.operations.SyncOp;
public abstract class TstAbstractConnector implements CreateOp, SearchOp<Filter>, SyncOp, DeleteOp {
private static final class ResourceComparator implements Comparator<ConnectorObject> {
private final List<SortKey> sortKeys;
private ResourceComparator(final SortKey... sortKeys) {
this.sortKeys = Arrays.asList(sortKeys);
}
public int compare(final ConnectorObject r1, final ConnectorObject r2) {
for (final SortKey sortKey : sortKeys) {
final int result = compare(r1, r2, sortKey);
if (result != 0) {
return result;
}
}
return 0;
}
private int compare(final ConnectorObject r1, final ConnectorObject r2,
final SortKey sortKey) {
final List<Object> vs1 = getValuesSorted(r1, sortKey.getField());
final List<Object> vs2 = getValuesSorted(r2, sortKey.getField());
if (vs1.isEmpty() && vs2.isEmpty()) {
return 0;
} else if (vs1.isEmpty()) {
// Sort resources with missing attributes last.
return 1;
} else if (vs2.isEmpty()) {
// Sort resources with missing attributes last.
return -1;
} else {
final Object v1 = vs1.get(0);
final Object v2 = vs2.get(0);
return sortKey.isAscendingOrder() ? compareValues(v1, v2) : -compareValues(v1, v2);
}
}
private List<Object> getValuesSorted(final ConnectorObject resource, final String field) {
final Attribute value = AttributeUtil.find(field, resource.getAttributes());
if (value == null || value.getValue() == null || value.getValue().isEmpty()) {
return Collections.emptyList();
} else if (value.getValue().size() > 1) {
List<Object> results = new ArrayList<Object>(value.getValue());
Collections.sort(results, VALUE_COMPARATOR);
return results;
} else {
return value.getValue();
}
}
}
private static final Comparator<Object> VALUE_COMPARATOR = new Comparator<Object>() {
public int compare(final Object o1, final Object o2) {
return compareValues(o1, o2);
}
};
private static int compareValues(final Object v1, final Object v2) {
if (v1 instanceof String && v2 instanceof String) {
final String s1 = (String) v1;
final String s2 = (String) v2;
return s1.compareToIgnoreCase(s2);
} else if (v1 instanceof Number && v2 instanceof Number) {
final Double n1 = ((Number) v1).doubleValue();
final Double n2 = ((Number) v2).doubleValue();
return n1.compareTo(n2);
} else if (v1 instanceof Boolean && v2 instanceof Boolean) {
final Boolean b1 = (Boolean) v1;
final Boolean b2 = (Boolean) v2;
return b1.compareTo(b2);
} else {
return v1.getClass().getName().compareTo(v2.getClass().getName());
}
}
protected TstStatefulConnectorConfig config;
public void init(Configuration cfg) {
config = (TstStatefulConnectorConfig) cfg;
config.getGuid();
}
public Uid create(ObjectClass objectClass, Set<Attribute> createAttributes,
OperationOptions options) {
AttributesAccessor accessor = new AttributesAccessor(createAttributes);
if (accessor.hasAttribute("fail")) {
throw new ConnectorException("Test Exception");
} else if (accessor.hasAttribute("exist") && accessor.findBoolean("exist")) {
throw new AlreadyExistsException(accessor.getName().getNameValue());
} else if (accessor.hasAttribute("emails")) {
Object value = AttributeUtil.getSingleValue(accessor.find("emails"));
if (value instanceof Map) {
return new Uid((String) ((Map) value).get("email"));
} else {
throw new InvalidAttributeValueException("Expecting Map");
}
}
return new Uid(config.getGuid().toString());
}
public void delete(ObjectClass objectClass, Uid uid, OperationOptions options) {
if (null == uid.getRevision()) {
throw new PreconditionRequiredException("Version is required for MVCC");
} else if (config.getGuid().toString().equals(uid.getRevision())) {
// Delete
} else {
throw new PreconditionFailedException(
"Current version of resource is 0 and not match with: " + uid.getRevision());
}
}
public FilterTranslator<Filter> createFilterTranslator(ObjectClass objectClass,
OperationOptions options) {
return new FilterTranslator<Filter>() {
public List<Filter> translate(Filter filter) {
return Collections.singletonList(filter);
}
};
}
public void executeQuery(ObjectClass objectClass, Filter query, ResultsHandler handler,
OperationOptions options) {
SortKey[] sortKeys = options.getSortKeys();
if (null == sortKeys) {
sortKeys = new SortKey[] { new SortKey(Name.NAME, true) };
}
// Rebuild the full result set.
TreeSet<ConnectorObject> resultSet =
new TreeSet<ConnectorObject>(new ResourceComparator(sortKeys));
if (null != query) {
for (ConnectorObject co : collection.values()) {
if (query.accept(co)) {
resultSet.add(co);
}
}
} else {
resultSet.addAll(collection.values());
}
// Handle the results
if (null != options.getPageSize()) {
// Paged Search
final String pagedResultsCookie = options.getPagedResultsCookie();
String currentPagedResultsCookie = options.getPagedResultsCookie();
final Integer pagedResultsOffset =
null != options.getPagedResultsOffset() ? Math.max(0, options
.getPagedResultsOffset()) : 0;
final Integer pageSize = options.getPageSize();
int index = 0;
int pageStartIndex = null == pagedResultsCookie ? 0 : -1;
int handled = 0;
for (ConnectorObject entry : resultSet) {
if (pageStartIndex < 0 && pagedResultsCookie.equals(entry.getName().getNameValue())) {
pageStartIndex = index + 1;
}
if (pageStartIndex < 0 || index < pageStartIndex) {
index++;
continue;
}
if (handled >= pageSize) {
break;
}
if (index >= pagedResultsOffset + pageStartIndex) {
if (handler.handle(entry)) {
handled++;
currentPagedResultsCookie = entry.getName().getNameValue();
} else {
break;
}
}
index++;
}
if (index == resultSet.size()) {
currentPagedResultsCookie = null;
}
if (handler instanceof SearchResultsHandler) {
((SearchResultsHandler) handler).handleResult(new SearchResult(
currentPagedResultsCookie, resultSet.size() - index));
}
} else {
// Normal Search
for (ConnectorObject entry : resultSet) {
if (!handler.handle(entry)) {
break;
}
}
if (handler instanceof SearchResultsHandler) {
((SearchResultsHandler) handler).handleResult(new SearchResult());
}
}
}
public void sync(ObjectClass objectClass, SyncToken token, SyncResultsHandler handler,
OperationOptions options) {
if (handler instanceof SyncTokenResultsHandler) {
((SyncTokenResultsHandler) handler).handleResult(getLatestSyncToken(objectClass));
}
}
public SyncToken getLatestSyncToken(ObjectClass objectClass) {
return new SyncToken(config.getGuid().toString());
}
private final static SortedMap<String, ConnectorObject> collection =
new TreeMap<String, ConnectorObject>(String.CASE_INSENSITIVE_ORDER);
static {
boolean enabled = true;
for (int i = 0; i < 100; i++) {
ConnectorObjectBuilder builder = new ConnectorObjectBuilder();
builder.setUid(String.valueOf(i));
builder.setName(String.format("user%03d", i));
builder.addAttribute(AttributeBuilder.buildEnabled(enabled));
Map<String, Object> mapAttribute = new HashMap<String, Object>();
mapAttribute.put("email", "foo@example.com");
mapAttribute.put("primary", true);
mapAttribute.put("usage", Arrays.asList("home", "work"));
builder.addAttribute(AttributeBuilder.build("emails", mapAttribute));
ConnectorObject co = builder.build();
collection.put(co.getName().getNameValue(), co);
enabled = !enabled;
}
}
}