/**
* Copyright 2007-2008 University Of Southern California
*
* 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 edu.isi.pegasus.planner.parser;
import edu.isi.pegasus.common.logging.LogManager;
import edu.isi.pegasus.planner.common.PegasusProperties;
import edu.isi.pegasus.planner.parser.pdax.Callback;
import edu.isi.pegasus.planner.partitioner.Partition;
import edu.isi.pegasus.planner.partitioner.graph.GraphNode;
import edu.isi.pegasus.common.util.FactoryException;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import java.io.File;
import java.util.HashMap;
import java.util.List;
/**
* This is a parser class for the parsing the pdax that contain the jobs in the
* various partitions and the relations between the partitions.
*
* @author Karan Vahi
* @version $Revision$
*/
public class PDAXParser extends Parser {
/**
* The "not-so-official" location URL of the DAX schema definition.
*/
public static final String SCHEMA_LOCATION =
"http://pegasus.isi.edu/schema/pdax-2.0.xsd";
/**
* URI namespace
*/
public static final String SCHEMA_NAMESPACE =
"http://pegasus.isi.edu/schema/PDAX";
/**
* The object holding the contents of one partition as indicated in the
* pdax.
*/
private Partition mPartition;
/**
* The current depth of parsing through the xml structure.
*/
private int mCurrentDepth;
/**
* The current child.
*/
private String mChild;
/**
* List of parents for a particular child.
*/
private List mParents;
/**
* The callback handler to which the callbacks are sent during designated
* points of parsing the pdax.
*/
private Callback mCallback;
/**
* The default constructor.
*
* @param properties the <code>PegasusProperties</code> to be used.
*/
public PDAXParser( PegasusProperties properties ) {
super( properties );
//intialize to null every member variable
mPartition = null;
mCurrentDepth = 0;
mCallback = null;
}
/**
* The constructor initialises the parser, and turns on the validation feature
* in Xerces.
*
* @param fileName the file which one has to parse using the parser.
* @param properties the <code>PegasusProperties</code> to be used.
*/
public PDAXParser( String fileName, PegasusProperties properties ) {
super( properties );
mCurrentDepth = 0;
try{
this.testForFile(fileName);
}
catch( Exception e){
throw new RuntimeException( e );
}
mCallback = null;
//set the schema location against which
//to validate.
String schemaLoc = getSchemaLocation();
mLogger.log("Picking schema " + schemaLoc,LogManager.CONFIG_MESSAGE_LEVEL);
String list = PDAXParser.SCHEMA_NAMESPACE + " " + schemaLoc;
setSchemaLocations(list);
}
/**
* Returns the XML schema namespace that a document being parsed conforms
* to.
*
* @return the schema namespace
*/
public String getSchemaNamespace( ){
return PDAXParser.SCHEMA_NAMESPACE;
}
/**
* Sets the callback handler for this parsing instance.
*/
public void setCallback(Callback callback){
mCallback = callback;
}
/**
* Ends up starting the parsing of the file , by the underlying parser.
*
* @param file the path/url to the file that needs to be parsed.
*/
public void startParser(String file) {
try {
mParser.parse(file);
}
catch( FactoryException fe){
//throw it as it is for time being
throw fe;
}
catch (Exception e) {
String message;
//if a locator error then
if(mLocator != null){
message = "Parsing Error in " + mLocator.getSystemId() +
" at line " + mLocator.getLineNumber() +
" at column " + mLocator.getColumnNumber() +
" : " ;
}
else{
message = "Parsing the PDAX file " ;
mLogger.log(message, LogManager.ERROR_MESSAGE_LEVEL);
}
throw new RuntimeException(message, e);
}
}
/**
* An empty implementation is provided by DefaultHandler of ContentHandler.
* This method receives the notification from the sacks parser when start
* tag of an element comes. Any parser class must implement this method.
*/
public void startElement(String uri, String local, String raw,
Attributes attrs) throws SAXException{
String key;
String value;
int i = 0;
//new element increment the depth
mCurrentDepth++;
if(local.equals("pdag")){
HashMap mp = new HashMap();
for(i = 0; i < attrs.getLength(); i++){
key = attrs.getLocalName(i);
value = attrs.getValue(i);
//should probably check for valid attributes before setting
mp.put(key,value);
//System.out.println(key + " --> " + value);
}
//call the callback interface
mCallback.cbDocument(mp);
return;
}
else if(local.equals("partition")){
mPartition = new Partition();
for(i = 0; i < attrs.getLength(); i++){
key = attrs.getLocalName(i);
value = attrs.getValue(i);
//check for valid attributes before setting
if(key.equals("name")){
mPartition.setName(value);
}
else if(key.equals("id")){
mPartition.setID(value);
}
else if(key.equals("index")){
int index = -1;
//try convert the String to int
try{
index = Integer.parseInt(value);
}
catch(Exception e){
invalidValue(local,key,value);
}
mPartition.setIndex(index);
}
else{
invalidAttribute(local,key,value);
}
//System.out.println(key + " --> " + value);
}
return;
}
else if(local.equals("job")){
String name = null;
String id = null;
GraphNode job;
for(i = 0; i < attrs.getLength(); i++){
key = attrs.getLocalName(i);
value = attrs.getValue(i);
//check for valid attributes before setting
if (key.equals("name")) {
name = value;
}
else if(key.equals("id")){
id = value;
}
else{
//complain about invalid key
invalidAttribute(local,key,value);
}
}
job = new GraphNode(id,name);
//add it to the partition
mPartition.addNode(job);
return;
}
else if(local.equals("child")){
//we do not know how many parents it has
mParents = new java.util.LinkedList();
for(i = 0; i < attrs.getLength(); i++){
key = attrs.getLocalName(i);
value = attrs.getValue(i);
if(key.equals("ref")){
mChild = value;
}
else{
invalidAttribute(local,key,value);
}
}
return;
}
else if(local.equals("parent")){
for( i = 0; i < attrs.getLength(); i++){
key = attrs.getLocalName(i);
value = attrs.getValue(i);
if(key.equals("ref")){
mParents.add(value);
}
else{
invalidAttribute(local,key,value);
}
}
return;
}
else {
mLogger.log("No implementation for element " + local,
LogManager.ERROR_MESSAGE_LEVEL );
throw new RuntimeException( "No implementation for element " + local );
}
}
/**
* An empty implementation is provided by DefaultHandler class. This method
* is called automatically by the Sax parser when the end tag of an element
* comes in the xml file. Any parser class should implement this method
*/
public void endElement(String uri, String local, String qName){
//decrement the depth of parsing
mCurrentDepth--;
if(local.equals("pdag")){
//call the callback interface
return;
}
else if(local.equals("partition")){
//call the callback interface
mCallback.cbPartition(mPartition);
//cleanup the object
mPartition = null;
}
else if(local.equals("child")){
//check if it was nested in partition element
//or the pdag element
if(mCurrentDepth == 2){
//means the put the child and parents in partition
mPartition.addParents(mChild,mParents);
}
else if(mCurrentDepth == 1){
//need to call the callback interface
mCallback.cbParents(mChild,mParents);
}
else{
throw new RuntimeException( "Wrongly formed xml" );
}
}
else if(local.equals("parent") || local.equals("job")){
//do nothing
return;
}
else{
//end of invalid element.
//non reachable line???
mLogMsg = "End of invalid element reached " + local;
mLogMsg = (mLocator == null) ?
mLogMsg:
//append the locator information
mLogMsg + " at line " + mLocator.getLineNumber() +
" at column " + mLocator.getColumnNumber();
throw new RuntimeException( mLogMsg );
}
}
/**
* This is called automatically when the end of the XML file is reached.
*/
public void endDocument(){
//do a sanity check
if(mCurrentDepth != 0){
mLogger.log("It seems that the xml was not well formed!!",
LogManager.ERROR_MESSAGE_LEVEL);
}
//call the callback interface
mCallback.cbDone();
}
/**
* Helps the load database to locate the PDAX XML schema, if available.
* Please note that the schema location URL in the instance document
* is only a hint, and may be overriden by the findings of this method.
*
* @return a location pointing to a definition document of the XML
* schema that can read PDAX. Result may be null, if such a document
* is unknown or unspecified.
*/
public String getSchemaLocation(){
String child = new File(this.SCHEMA_LOCATION).getName();
File pdax = // create a pointer to the default local position
new File(this.mProps.getSysConfDir(), child);
//System.out.println("\nDefault Location of PDAX is " + pdax.getAbsolutePath());
// Nota bene: vds.schema.dax may be a networked URI...
return this.mProps.getPDAXSchemaLocation(pdax.getAbsolutePath());
}
/**
* Logs a message if an unknown key is come across, while parsing the
* xml document.
*
* @param element the xml element in which the invalid key was come across.
* @param key the key that is construed to be invalid.
* @param value the value associated with the key.
*/
private void invalidAttribute(String element, String key, String value){
String message = "Invalid attribute " + key + "found in " + element +
" with value " + value;
message = (mLocator == null) ?
message:
//append the locator information
message + " at line " + mLocator.getLineNumber() +
" at column " + mLocator.getColumnNumber();
mLogger.log(message, LogManager.WARNING_MESSAGE_LEVEL);
}
/**
* Logs a message if an unknown value is come across, while parsing the
* xml document.
*
* @param element the xml element in which the invalid key was come across.
* @param key the key that is construed to be invalid.
* @param value the value associated with the key.
*/
private void invalidValue(String element, String key, String value){
String message = "Invalid value " + value + "found in " + element +
" for attribute " + value;
message = (mLocator == null) ?
message:
//append the locator information
message + " at line " + mLocator.getLineNumber() +
" at column " + mLocator.getColumnNumber();
mLogger.log(message,LogManager.WARNING_MESSAGE_LEVEL);
}
}