/*==========================================================================*\
| $Id: Submitter.java,v 1.6 2010/12/06 21:06:48 aallowat Exp $
|*-------------------------------------------------------------------------*|
| Copyright (C) 2006-2009 Virginia Tech
|
| This file is part of Web-CAT Electronic Submitter.
|
| Web-CAT is free software; you can redistribute it and/or modify
| it under the terms of the GNU General Public License as published by
| the Free Software Foundation; either version 2 of the License, or
| (at your option) any later version.
|
| Web-CAT is distributed in the hope that it will be useful,
| but WITHOUT ANY WARRANTY; without even the implied warranty of
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
| GNU General Public License for more details.
|
| You should have received a copy of the GNU General Public License along
| with Web-CAT; if not, see <http://www.gnu.org/licenses/>.
\*==========================================================================*/
package org.webcat.submitter;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.webcat.submitter.internal.DefaultLongRunningTaskManager;
import org.webcat.submitter.internal.LongRunningTask;
import org.webcat.submitter.internal.SubmissionParserErrorHandler;
import org.webcat.submitter.internal.utility.PathMatcher;
import org.webcat.submitter.targets.AssignmentTarget;
import org.webcat.submitter.targets.RootTarget;
import org.webcat.submitter.targets.SubmissionTarget;
import org.xml.sax.SAXException;
//--------------------------------------------------------------------------
/**
* <p>
* The primary class providing functionality for the electronic submitter.
* Usage example:
* </p>
* <pre>
* java.io.File folder = new java.io.File("path to what you want to submit");
* SubmittableFile itemToSubmit = new SubmittableFile(folder);
*
* Submitter submitter = new Submitter();
* URL targetsURL = new URL("http://yoursite.com/targets.xml");
* submitter.readSubmissionTargets(targetsURL);
* RootTarget root = submitter.getRoot();
*
* // Traverse the submission targets from root and get the assignment that you
* // wish to submit to, perhaps via a user interface.
* AssignmentTarget assignment = ...;
*
* SubmissionManifest manifest = new SubmissionManifest();
* manifest.setSubmittableItems(itemToSubmit);
* manifest.setAssignment(assignment);
* manifest.setUsername("username");
* manifest.setPassword("password");
*
* submitter.submit(manifest);
* </pre>
*
* @author Tony Allevato (Virginia Tech Computer Science)
* @author latest changes by: $Author: aallowat $
* @version $Revision: 1.6 $ $Date: 2010/12/06 21:06:48 $
*/
public class Submitter
{
//~ Constructors ..........................................................
// ----------------------------------------------------------
/**
* Creates a new submitter.
*/
public Submitter()
{
taskManager = new DefaultLongRunningTaskManager();
}
//~ Methods ...............................................................
// ----------------------------------------------------------
/**
* Gets the long running task manager used by this submitter.
*
* @return the long running task manager used by this submitter
*/
public ILongRunningTaskManager getLongRunningTaskManager()
{
return taskManager;
}
// ----------------------------------------------------------
/**
* <p>
* Sets the long running task manager to be used by this submitter.
* </p><p>
* You may pass <code>null</code> to this method as a shortcut to revert
* to the default long running task manager.
* </p>
*
* @param manager the long running task manager to be used by this
* submitter
*/
public void setLongRunningTaskManager(ILongRunningTaskManager manager)
{
if (manager == null)
{
taskManager = new DefaultLongRunningTaskManager();
}
else
{
taskManager = manager;
}
}
// ----------------------------------------------------------
/**
* Gets the root object of the submission target tree.
*
* @return an ITargetRoot represented the root of the submission targets
*/
public RootTarget getRoot()
{
return root;
}
// ----------------------------------------------------------
/**
* <p>
* Gets the target at the specified path, where the path is a
* slash-delimited sequence of target names. If a target name itself
* contains a slash, it should be escaped as a double-slash (for example,
* the path "Target A/Target//B" would search for a child of the
* root named "Target A", then a child of that target named "Target/B".
* </p><p>
* If two children of a target have the same name, this method will only
* find the first one. To avoid this, always use distinct names for the
* immediate children of any particular node.
* </p><p>
* This method will not find hidden targets, even if the name matches the
* path passed in. This is by design.
* </p>
*
* @param path the path from the root
* @return the target found at the specified path, or null if one was not
* found
* @throws SubmissionTargetException if an exception occurs
*/
public SubmissionTarget getTarget(String path)
throws SubmissionTargetException
{
SubmissionTarget target = getRoot();
String[] components = path.split("/(?!/)");
for (String component : components)
{
component = component.replaceAll("//", "/");
SubmissionTarget foundChild = null;
SubmissionTarget[] children = target.getLogicalChildren();
for (SubmissionTarget child : children)
{
if (!child.isHidden() && component.equals(child.getName()))
{
foundChild = child;
break;
}
}
if (foundChild != null)
{
target = foundChild;
}
else
{
target = null;
break;
}
}
return target;
}
// ----------------------------------------------------------
/**
* Reads the submission target definitions from the specified URL.
*
* @param definitionsUrl a URL that points to the submission target
* definitions
* @throws IOException if an I/O exception occurred
* @throws SubmissionTargetException if some other exception occurred
*/
public void readSubmissionTargets(URL definitionsUrl)
throws IOException
{
InputStream stream = null;
try
{
stream = definitionsUrl.openStream();
readSubmissionTargets(stream);
}
finally
{
try
{
if (stream != null)
{
stream.close();
}
}
catch (IOException e)
{
// Do nothing.
}
}
}
// ----------------------------------------------------------
/**
* Reads the submission target definitions from the specified input stream.
*
* @param stream the InputStream from which to read the submission targets
* @throws IOException if an I/O exception occurred
* @throws SubmissionTargetException if some other exception occurred
*/
public void readSubmissionTargets(final InputStream stream)
throws IOException
{
LongRunningTask task = new LongRunningTask(
"Reading submission targets") {
public void run() throws Exception
{
beginSubtask(1);
try
{
DocumentBuilderFactory factory =
DocumentBuilderFactory.newInstance();
factory.setIgnoringComments(true);
factory.setCoalescing(false);
factory.setNamespaceAware(true);
factory.setValidating(false);
SubmissionParserErrorHandler errorHandler =
new SubmissionParserErrorHandler();
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setErrorHandler(errorHandler);
Document document = builder.parse(stream);
TargetParseError[] errors = errorHandler.getErrors();
if (errors != null)
{
throw new TargetParseException(errors);
}
else
{
root = new RootTarget(taskManager);
root.parse(document.getDocumentElement(), this);
}
}
catch (ParserConfigurationException e)
{
throw new SubmissionTargetException(e);
}
catch (SAXException e)
{
throw new SubmissionTargetException(e);
}
finally
{
finishSubtask();
}
}
};
try
{
taskManager.run(task);
}
catch (InvocationTargetException e)
{
throw (IOException) e.getCause();
}
}
// ----------------------------------------------------------
/**
* Gets a value indicating whether the last submission generated a
* response.
*
* @return true if the last submission generated a response; otherwise,
* false
*/
public boolean hasResponse()
{
return hasResponse;
}
// ----------------------------------------------------------
/**
* Gets the string contents of the response generated by the last
* submission, if one was generated.
*
* @return the response from the last submission
*/
public String getResponse()
{
return response;
}
// ----------------------------------------------------------
/**
* Submits the resources described by the specified submission manifest.
*
* @param manifest a {@link SubmissionManifest} that describes the items to
* be submitted and where they should be sent
* @throws IOException
* @throws ProtocolNotRegisteredException
* @throws RequiredItemsMissingException
* @throws SubmissionTargetException
*/
public void submit(final SubmissionManifest manifest)
throws IOException, SubmissionTargetException
{
if (manifest.getSubmittableItems() == null ||
manifest.getSubmittableItems().length == 0)
{
throw new NoItemsToSubmitException();
}
hasResponse = false;
response = null;
String[] missingItemPatterns =
verifyRequiredItems(manifest.getAssignment(),
manifest.getSubmittableItems());
if (missingItemPatterns != null)
{
throw new RequiredItemsMissingException(missingItemPatterns);
}
// It doesn't actually matter what encoder we use here, since we're
// only getting the URI in order to strip off the scheme and determine
// which transport protocol to use.
URI transport = manifest.getResolvedTransport(null);
final IProtocol protocol =
ProtocolRegistry.getInstance().createProtocolInstance(
transport.getScheme());
if (protocol != null)
{
LongRunningTask task = new LongRunningTask(
"Packaging and submitting files") {
public void run() throws Exception
{
protocol.submit(manifest, this);
}
};
try
{
taskManager.run(task);
}
catch (InvocationTargetException e)
{
Throwable cause = e.getCause();
if (cause instanceof RuntimeException)
{
throw (RuntimeException) cause;
}
else if (cause instanceof IOException)
{
throw (IOException) cause;
}
else
{
cause.printStackTrace();
}
}
if (protocol.hasResponse())
{
hasResponse = true;
response = protocol.getResponse();
}
}
else
{
throw new ProtocolNotRegisteredException(transport.getScheme());
}
}
// ----------------------------------------------------------
/**
* Verifies that all of the files that are denoted as required by the
* target assignment exist in the set of submittable items being packaged.
*
* @param assignment the assignment to which the items are being submitted
* @param items the items being submitted
* @return an array of required file patterns that were not satisfied by
* the specified set of submittable items, or null if all required
* files were present
*/
private String[] verifyRequiredItems(AssignmentTarget assignment,
ISubmittableItem[] items) throws SubmissionTargetException
{
final Map<String, Boolean> requiredItemPatterns =
new Hashtable<String, Boolean>();
final String[] patterns = assignment.getAllRequiredFiles();
for (String pattern : patterns)
{
requiredItemPatterns.put(pattern, false);
}
SubmittableItemVisitor visitor = new SubmittableItemVisitor()
{
protected void accept(ISubmittableItem item)
{
if (item.getKind() == SubmittableItemKind.FILE)
{
for (String reqPattern : patterns)
{
PathMatcher pattern = new PathMatcher(reqPattern);
if (pattern.matches(item.getFilename()))
{
requiredItemPatterns.put(reqPattern, true);
}
}
}
}
};
try
{
visitor.visit(items);
}
catch (InvocationTargetException e)
{
// Do nothing; no exceptions should be thrown.
}
List<String> missingItemPatterns = new ArrayList<String>();
for (String requiredPattern : requiredItemPatterns.keySet())
{
if (requiredItemPatterns.get(requiredPattern) == false)
{
missingItemPatterns.add(requiredPattern);
}
}
if (missingItemPatterns.size() == 0)
{
return null;
}
else
{
String[] array = new String[missingItemPatterns.size()];
missingItemPatterns.toArray(array);
return array;
}
}
//~ Static/instance variables .............................................
/* The root of the submission target object tree. */
private RootTarget root;
/* Indicates whether the submission generated a response. */
private boolean hasResponse;
/* Contains the response generated by the submission, if any. */
private String response;
/* Used to manage progress notifications for long-running tasks. */
private ILongRunningTaskManager taskManager;
}