/*
* ====================
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved.
*
* The contents of this file are subject to the terms of the Common Development
* and Distribution License("CDDL") (the "License"). You may not use this file
* except in compliance with the License.
*
* You can obtain a copy of the License at
* http://opensource.org/licenses/cddl1.php
* See the License for the specific language governing permissions and limitations
* under the License.
*
* When distributing the Covered Code, include this CDDL Header Notice in each file
* and include the License file at http://opensource.org/licenses/cddl1.php.
* If applicable, add the following below this CDDL Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
* ====================
*/
package org.identityconnectors.solaris.operation.search;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.identityconnectors.common.logging.Log;
import org.identityconnectors.framework.common.objects.Attribute;
import org.identityconnectors.framework.common.objects.AttributeInfo;
import org.identityconnectors.framework.common.objects.AttributeUtil;
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.ObjectClassInfo;
import org.identityconnectors.framework.common.objects.OperationOptions;
import org.identityconnectors.framework.common.objects.OperationalAttributes;
import org.identityconnectors.framework.common.objects.ResultsHandler;
import org.identityconnectors.framework.common.objects.Schema;
import org.identityconnectors.framework.common.objects.Uid;
import org.identityconnectors.solaris.SolarisConfiguration;
import org.identityconnectors.solaris.SolarisConnection;
import org.identityconnectors.solaris.SolarisConnector;
import org.identityconnectors.solaris.SolarisUtil;
import org.identityconnectors.solaris.attr.AccountAttribute;
import org.identityconnectors.solaris.attr.AttrUtil;
import org.identityconnectors.solaris.attr.ConnectorAttribute;
import org.identityconnectors.solaris.attr.GroupAttribute;
import org.identityconnectors.solaris.attr.NativeAttribute;
import org.identityconnectors.solaris.mode.ActivationMode;
import org.identityconnectors.solaris.operation.AbstractOp;
import org.identityconnectors.solaris.operation.search.nodes.AcceptAllNode;
import org.identityconnectors.solaris.operation.search.nodes.EqualsNode;
import org.identityconnectors.solaris.operation.search.nodes.Node;
public class SolarisSearch extends AbstractOp {
private static final Log logger = Log.getLog(SolarisSearch.class);
private SolarisConnection connection;
/**
* SHELL objectClass supports only Search operation. It encapsulates the
* shell types that are available on the given Solaris resource.
*/
public static final ObjectClass SHELL = new ObjectClass("shell");
private final ObjectClass oclass;
final ObjectClass[] acceptOC = { ObjectClass.ACCOUNT, ObjectClass.GROUP, SHELL };
private final Node filter;
private final ResultsHandler handler;
/** original set of attributes to get, contains {@see ConnectorAttribute}-s. */
private final String[] attrsToGet;
/** names of attributes to get translated to {@see NativeAttribute}-s. */
private final Set<NativeAttribute> attrsToGetNative;
/**
*
* @param connector
* @param oclass
* @param filter
* contains the filters. Is created by
* {@link SolarisFilterTranslator}
* @param handler
* @param options
*/
public SolarisSearch(SolarisConnector connector, ObjectClass oclass, Node filter,
ResultsHandler handler, OperationOptions options) {
super(connector);
connection = connector.getConnection();
SolarisConfiguration config = connection.getConfiguration();
this.oclass = oclass;
this.handler = handler;
if (filter == null) {
// NULL indicates that we should return all results.
this.filter = new AcceptAllNode();
} else {
this.filter = filter;
}
if (oclass.is(SHELL.getObjectClassValue())) {
attrsToGet = null;
attrsToGetNative = null;
return;
}
/** attributes to get init */
String[] attrsToGet = options.getAttributesToGet();
if (attrsToGet == null) {
// if no attributes to get, return all RETURNED_BY_DEFAULT
// attributes
attrsToGet = getReturnedByDefaultAttrs(getSchema());
}
this.attrsToGet = attrsToGet;
// translate attrsToGet from Connector to Native attributes:
Set<NativeAttribute> translatedAttrs = new HashSet<NativeAttribute>(attrsToGet.length);
if (oclass.is(ObjectClass.ACCOUNT_NAME)) {
for (String accountAttrName : attrsToGet) {
if (Uid.NAME.equalsIgnoreCase(accountAttrName)) {
translatedAttrs.add(AccountAttribute.NAME.getNative());
} else if (OperationalAttributes.ENABLE_NAME.equals(accountAttrName)) {
if (ActivationMode.EXPIRATION.getConfigString().equals(config.getActivationMode())) {
translatedAttrs.add(NativeAttribute.USER_EXPIRE);
} else if (ActivationMode.LOCKING.getConfigString().equals(config.getActivationMode())) {
translatedAttrs.add(NativeAttribute.LOCK);
} else if (ActivationMode.NONE.getConfigString().equals(config.getActivationMode())) {
// nothing to do
} else {
throw new IllegalArgumentException("Unknown activation mode "+config.getActivationMode());
}
} else {
translatedAttrs.add(AccountAttribute.forAttributeName(accountAttrName).getNative());
}
}
} else if (oclass.is(ObjectClass.GROUP_NAME)) {
for (String groupAttrName : attrsToGet) {
if (Uid.NAME.equalsIgnoreCase(groupAttrName)) {
translatedAttrs.add(GroupAttribute.GROUPNAME.getNative());
} else {
translatedAttrs.add(GroupAttribute.forAttributeName(groupAttrName).getNative());
}
}
}
attrsToGetNative = translatedAttrs;
}
/**
* Search operation.
*
*/
public void executeQuery() {
logger.info("search ({0})", filter.toString());
SolarisUtil.controlObjectClassValidity(oclass, acceptOC, getClass(), connection
.getConfiguration());
if (oclass.is(SHELL.getObjectClassValue())) {
final String cmd = "[ -f \"/etc/shells\" ] && cat /etc/shells";
final String out = connection.executeCommand(cmd);
final List<String> items = parseResult(out);
notifyHandler(oclass, handler, items);
return;
}
/* required attributes inside the search (!= attrsToGet) */
Set<NativeAttribute> requiredAttrs =
new HashSet<NativeAttribute>(NativeAttribute.values().length);
// 1) retrieve native attributes from the Node
filter.collectAttributeNames(requiredAttrs);
// 2) make union of attributes inside filter AND attributesToGet given
// from client.
requiredAttrs.addAll(attrsToGetNative);
if (filter instanceof EqualsNode
&& ((EqualsNode) filter).getAttributeName().equals(NativeAttribute.NAME)) {
simpleSearch(oclass, requiredAttrs);
} else {
complexSearch(oclass, requiredAttrs);
}
logger.info("search successfully finished.");
}
/**
* Notify the given handler with results of type {@code oclass}.
*
* @param oclass
* the objectclass type that the results belong to.
* @param h
* the handler.
* @param items
* the result items that we will propagate to handler
*/
private void notifyHandler(ObjectClass oclass, ResultsHandler h, List<String> items) {
for (String it : items) {
// This is how ConnectorAdapter awaits the results, the value should
// be in a __NAME__ attribute.
ConnectorObject co =
new ConnectorObjectBuilder().setObjectClass(oclass).addAttribute(Name.NAME, it)
.addAttribute(Uid.NAME, it).build();
handler.handle(co);
}
}
/**
* Parse useful strings separated by newline from the given
* {@code commandOutput}.
*
* @param commandOutput
* the output of some command, where items are separated by new
* line. If the line starts with {@code #} character, it will be
* skipped (as a comment).
*/
private List<String> parseResult(String commandOutput) {
String[] lines = commandOutput.split("\n");
List<String> items = new ArrayList<String>(lines.length);
for (String line : lines) {
if (line.startsWith("#")) {
continue;
}
String trimmedLine = line.trim();
if (trimmedLine.length() > 0) {
items.add(trimmedLine);
}
}
return items;
}
/**
* COMPLEX filtering, requires evaluation of the filter tree ({@see Node}).
*
* <p>
* Only {@link ObjectClass#GROUP} and {@link ObjectClass#ACCOUNT} are
* allowed types in this method.
*
* @param oclass2
* object class type
* @param requiredAttrs
* the attributes that we want to fetch.
*/
private void complexSearch(ObjectClass oclass2, Set<NativeAttribute> requiredAttrs) {
Iterator<SolarisEntry> entryIt =
(oclass2.is(ObjectClass.ACCOUNT_NAME)) ? SolarisEntries.getAllAccounts(
requiredAttrs, connection) : SolarisEntries.getAllGroups(requiredAttrs,
connection);
while (entryIt.hasNext()) {
final SolarisEntry entry = entryIt.next();
if (filter.evaluate(entry)) {
ConnectorObject connObj = SolarisUtil.convertToConnectorObject(entry, attrsToGet, oclass, connection.getConfiguration());
handler.handle(connObj);
}
}
}
/**
* SIMPLE filtering defined as an {@see EqualsNode} with a single {@see
* Name} attribute. For instance: userName = 'johnSmith'.
*
* <p>
* Only {@link ObjectClass#GROUP} and {@link ObjectClass#ACCOUNT} are
* allowed types in this method.
*
* @param oclass2
* objectClass type
* @param requiredAttrs
* the attributes the we want to fetch.
*/
private void simpleSearch(ObjectClass oclass2, Set<NativeAttribute> requiredAttrs) {
final SolarisEntry singleEntry;
if (oclass.is(ObjectClass.ACCOUNT_NAME)) {
singleEntry =
SolarisEntries.getAccount(((EqualsNode) filter).getSingleValue(),
requiredAttrs, connection);
} else { // GROUP
singleEntry =
SolarisEntries.getGroup(((EqualsNode) filter).getSingleValue(), requiredAttrs,
connection);
}
if (singleEntry != null) {
ConnectorObject connObj = SolarisUtil.convertToConnectorObject(singleEntry, attrsToGet, oclass, connection.getConfiguration());
handler.handle(connObj);
}
}
/**
* TODO enhancement::: this can be done in two ways: either you have a
* register of returned by default attributes in the connector, or you find
* it in the schema.
*
* @param schema
* @return set of attribute names that are returned by default
*/
private String[] getReturnedByDefaultAttrs(Schema schema) {
List<String> result = new ArrayList<String>();
ObjectClassInfo ocinfo = schema.findObjectClassInfo(oclass.getObjectClassValue());
Set<AttributeInfo> attrInfo = ocinfo.getAttributeInfo();
for (AttributeInfo attributeInfo : attrInfo) {
if (attributeInfo.isReturnedByDefault()) {
result.add(attributeInfo.getName());
}
}
return result.toArray(new String[0]);
}
}