blob: 50b3ebc5e2f0e4b4a3c7718c4af012dff2af21f1 [file] [log] [blame]
// Copyright 2011 Google Inc. All Rights Reserved.
//
// 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 adaptorlib.prebuilt;
import adaptorlib.AbstractAdaptor;
import adaptorlib.AdaptorContext;
import adaptorlib.AuthnIdentity;
import adaptorlib.AuthzStatus;
import adaptorlib.CommandStreamParser;
import adaptorlib.Config;
import adaptorlib.DocId;
import adaptorlib.DocIdPusher;
import adaptorlib.Request;
import adaptorlib.Response;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
/**
* Command Line Adaptor
*/
public class CommandLineAdaptor extends AbstractAdaptor {
private static final Logger log = Logger.getLogger(CommandLineAdaptor.class.getName());
private Charset encoding = Charset.forName("UTF-8");
private List<String> listerCommand;
private List<String> retrieverCommand;
private List<String> authorizerCommand;
private String authzDelimiter;
@Override
public void initConfig(Config config) {
// Setup default configuration values. The user is allowed to override them.
// Create a new configuration key for letting the user configure this
// adaptor.
config.addKey("commandline.lister.cmd", null);
config.addKey("commandline.retriever.cmd", null);
config.addKey("commandline.authorizer.delimeter", "\0");
// Change the default to automatically provide unzipped zip contents to the
// GSA.
config.overrideKey("adaptor.autoUnzip", "true");
}
private List<String> readCommandLineConfig(AdaptorContext context, String prefix) {
Map<String, String> config = context.getConfig().getValuesWithPrefix(prefix);
String commandString = config.get("cmd");
List<String> command = null;
if (commandString != null) {
command = new ArrayList<String>();
command.add(commandString);
for (int i = 1;; i++) {
String argument = config.get("arg" + i);
if (argument == null) {
break;
}
command.add(argument);
}
}
return command;
}
@Override
public void init(AdaptorContext context) throws Exception {
listerCommand = readCommandLineConfig(context, "commandline.lister.");
if (listerCommand == null) {
throw new RuntimeException("commandline.lister.cmd configuration property must be set.");
}
retrieverCommand = readCommandLineConfig(context, "commandline.retriever.");
if (retrieverCommand == null) {
throw new RuntimeException("commandline.retriever.cmd configuration property must be set.");
}
authorizerCommand = readCommandLineConfig(context, "commandline.authorizer.");
authzDelimiter = context.getConfig().getValue("commandline.authorizer.delimeter");
}
public void setListerCommand(List<String> commandWithArgs) {
this.listerCommand = new ArrayList<String>(commandWithArgs);
}
public List<String> getListerCommand() {
return Collections.unmodifiableList(listerCommand);
}
public void setRetrieverCommand(List<String> commandWithArgs) {
this.retrieverCommand = new ArrayList<String>(commandWithArgs);
}
public List<String> geRetrieverCommand() {
return Collections.unmodifiableList(retrieverCommand);
}
@Override
public void getDocIds(DocIdPusher pusher) throws IOException,
InterruptedException {
int commandResult;
Command command = newListerCommand();
try {
log.finest("Command: " + listerCommand);
String[] commandLine = listerCommand.toArray(new String[0]);
commandResult = command.exec(commandLine);
} catch (InterruptedException e) {
throw new IOException("Thread interrupted while waiting for external command.", e);
} catch (IOException e) {
throw new IOException("External command could not be executed.", e);
}
if (commandResult != 0) {
String errorOutput = new String(command.getStderr(), encoding);
throw new IOException("External command error. code = " + commandResult + ". Stderr: "
+ errorOutput);
}
CommandStreamParser parser = new CommandStreamParser(
new ByteArrayInputStream(command.getStdout()));
log.finest("Pushing Document IDs.");
pusher.pushRecords(parser.readFromLister());
}
/** Gives the bytes of a document referenced with id. */
@Override
public void getDocContent(Request req, Response resp) throws IOException {
DocId id = req.getDocId();
int commandResult;
Command command = newRetrieverCommand();
try {
Date lastCrawled = req.getLastAccessTime();
long lastCrawledMillis = 0;
if (lastCrawled != null) {
lastCrawledMillis = lastCrawled.getTime();
}
String[] commandLine = new String[retrieverCommand.size() + 2];
retrieverCommand.toArray(commandLine);
commandLine[retrieverCommand.size()] = id.getUniqueId();
commandLine[retrieverCommand.size() + 1] = Long.toString(lastCrawledMillis);
log.finest("Command: " + Arrays.asList(commandLine));
commandResult = command.exec(commandLine);
} catch (InterruptedException e) {
throw new IOException("Thread intrupted while waiting for external command.", e);
} catch (IOException e) {
throw new IOException("External command could not be executed.", e);
}
if (commandResult != 0) {
String errorOutput = new String(command.getStderr(), encoding);
throw new IOException("External command error. code=" + commandResult + ". Stderr: "
+ errorOutput);
}
CommandStreamParser parser = new CommandStreamParser(
new ByteArrayInputStream(command.getStdout()));
CommandStreamParser.RetrieverInfo retrieverInfo = parser.readFromRetriever();
if (!req.getDocId().equals(retrieverInfo.getDocId())) {
throw new IOException("requested document " + req.getDocId() + " does not match retrieved "
+ "document " + retrieverInfo.getDocId() + ".");
}
if (retrieverInfo.notFound()) {
resp.respondNotFound();
} else if (retrieverInfo.isUpToDate()) {
log.finest("Retriever: " + id.getUniqueId() + " is up to date.");
resp.respondNotModified();
} else {
if (retrieverInfo.getMimeType() != null) {
log.finest("Retriever: " + id.getUniqueId() + " has mime-type "
+ retrieverInfo.getMimeType());
resp.setContentType(retrieverInfo.getMimeType());
}
if (retrieverInfo.getMetadata() != null) {
log.finest("Retriever: " + id.getUniqueId() + " has metadata "
+ retrieverInfo.getMetadata());
resp.setMetadata(retrieverInfo.getMetadata());
}
if (retrieverInfo.getContents() != null) {
resp.getOutputStream().write(retrieverInfo.getContents());
} else {
throw new IOException("No content returned by retriever for " + req.getDocId() + ".");
}
}
}
/**
* {@inheritDoc}
*
* <p>This implementation provides access permissions for the {@code DocId}s in an \
* unmodifiable map based upon data returned by a command line authorizer.
* Permissions can have one of three values:
* {@link adaptorlib.AuthzStatus#PERMIT},
* {@link adaptorlib.AuthzStatus#DENY},
* {@link adaptorlib.AuthzStatus#INDETERMINATE}
* If an authorizerCommand is not set then AbstractAdaptor.isUserAuthorized
* is used.
*/
@Override
public Map<DocId, AuthzStatus> isUserAuthorized(AuthnIdentity userIdentity,
Collection<DocId> ids) throws IOException {
if (authorizerCommand == null) {
return super.isUserAuthorized(userIdentity, ids);
}
StringBuilder stdinStringBuilder = new StringBuilder();
// Write out the user name
if (userIdentity.getUsername().contains(authzDelimiter)) {
throw new IllegalArgumentException("Error - Group '" + userIdentity.getUsername() +
"' contains the delimiter '" + authzDelimiter + "'");
}
stdinStringBuilder.append("GSA Adaptor Data Version 1 [" + authzDelimiter + "]"
+ authzDelimiter);
stdinStringBuilder.append("username=").append(userIdentity.getUsername())
.append(authzDelimiter);
// Write out the user password
if (userIdentity.getPassword() != null) {
if (userIdentity.getPassword().contains(authzDelimiter)) {
throw new IllegalArgumentException("Error - Password contains the delimiter '"
+ authzDelimiter + "'");
}
stdinStringBuilder.append("password=").append(userIdentity.getPassword())
.append(authzDelimiter);
}
// Write out the list of groups that this user belongs to
if (userIdentity.getGroups() != null) {
for (String group : userIdentity.getGroups()) {
if (group.contains(authzDelimiter)) {
throw new IllegalArgumentException("Group cannot contain the delimiter: "
+ authzDelimiter);
}
stdinStringBuilder.append("group=").append(group).append(authzDelimiter);
}
}
// Write out the list of document ids that are to be checked
for (DocId id : ids) {
if (id.getUniqueId().contains(authzDelimiter)) {
throw new IllegalArgumentException("Document ID cannot contain the delimiter: "
+ authzDelimiter);
}
stdinStringBuilder.append("id=").append(id.getUniqueId()).append(authzDelimiter);
}
String stdin = stdinStringBuilder.toString();
int commandResult;
Command command = newAuthorizerCommand();
try {
String[] commandLine = new String[authorizerCommand.size()];
authorizerCommand.toArray(commandLine);
log.finest("Command: " + Arrays.asList(commandLine));
commandResult = command.exec(commandLine, stdin.getBytes(encoding));
} catch (InterruptedException e) {
throw new IOException("Thread interrupted while waiting for external command.", e);
} catch (IOException e) {
throw new IOException("External command could not be executed.", e);
}
if (commandResult != 0) {
String errorOutput = new String(command.getStderr(), encoding);
throw new IOException("External command error. code = " + commandResult + ". Stderr: "
+ errorOutput);
}
CommandStreamParser parser = new CommandStreamParser(
new ByteArrayInputStream(command.getStdout()));
log.finest("Pushing Document IDs.");
return parser.readFromAuthorizer();
}
protected Command newListerCommand() {
return new Command();
}
protected Command newRetrieverCommand() {
return new Command();
}
protected Command newAuthorizerCommand() {
return new Command();
}
/** Call default main for adaptors. */
public static void main(String[] args) {
AbstractAdaptor.main(new CommandLineAdaptor(), args);
}
}