/* * Copyright (c) 2013 Red Hat, Inc. and/or its affiliates. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Cheng Fang - Initial API and implementation */ package org.jberet.job.model; import java.io.InputStream; import java.util.List; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLResolver; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import org.jberet._private.BatchMessages; import static javax.xml.stream.XMLStreamConstants.CDATA; import static javax.xml.stream.XMLStreamConstants.CHARACTERS; import static javax.xml.stream.XMLStreamConstants.END_ELEMENT; import static javax.xml.stream.XMLStreamConstants.START_ELEMENT; /** * StAX parser for batch job xml and batch xml files. */ public final class JobParser { private static final String namespaceURI = null; /** * Parses a job xml input stream, which defines a batch job. * * @param inputStream the input source of the job xml definition * @param classLoader the current application class loader * @param xmlResolver the {@code javax.xml.stream.XMLResolver} for the job xml, * typically obtained from {@code org.jberet.spi.BatchEnvironment#getJobXmlResolver()} * @return the {@code Job} from parsing the input source * @throws XMLStreamException if failed to parse the input source * * @see org.jberet.spi.BatchEnvironment#getJobXmlResolver() */ public static Job parseJob(final InputStream inputStream, final ClassLoader classLoader, final XMLResolver xmlResolver) throws XMLStreamException { final XMLInputFactory factory = XMLInputFactory.newInstance(); factory.setXMLResolver(xmlResolver); final XMLStreamReader reader = factory.createXMLStreamReader(inputStream); Job job = null; try { while (reader.hasNext()) { final int eventType = reader.next(); if (eventType != START_ELEMENT && eventType != END_ELEMENT) { continue; } final XmlElement element = XmlElement.forName(reader.getLocalName()); switch (eventType) { case START_ELEMENT: switch (element) { case JOB: job = new Job(getAttributeValue(reader, XmlAttribute.ID, true)); job.setRestartable(getAttributeValue(reader, XmlAttribute.RESTARTABLE, false)); job.setAbstract(getAttributeValue(reader, XmlAttribute.ABSTRACT, false)); final String parentVal = getAttributeValue(reader, XmlAttribute.PARENT, false); if (parentVal != null) { job.setParentAndJslName(parentVal, null); job.inheritingJobElements.add(job); } break; case STEP: job.addJobElement(parseStep(reader, job)); break; case FLOW: job.addJobElement(parseFlow(reader, job)); break; case SPLIT: job.addJobElement(parseSplit(reader, job)); break; case DECISION: job.addJobElement(parseDecision(reader)); break; case PROPERTIES: job.setProperties(parseProperties(reader)); break; case LISTENERS: job.setListeners(parseListeners(reader)); break; default: throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } break; case END_ELEMENT: if (element != XmlElement.JOB) { throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } } } } finally { reader.close(); } return job; } /** * Parses batch.xml, which declares batch artifacts in xml format. * * @param inputStream source of the batch.xml * @return {@code BatchArtifacts} object from parsing the input source * @throws XMLStreamException if failed to parse the input source */ public static BatchArtifacts parseBatchArtifacts(final InputStream inputStream) throws XMLStreamException { final XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(inputStream); BatchArtifacts batchArtifacts = null; try { while (reader.hasNext()) { final int eventType = reader.next(); if (eventType != START_ELEMENT && eventType != END_ELEMENT) { continue; } final XmlElement element = XmlElement.forName(reader.getLocalName()); switch (eventType) { case START_ELEMENT: switch (element) { case BATCH_ARTIFACTS: batchArtifacts = new BatchArtifacts(); break; case REF: batchArtifacts.addRef(getAttributeValue(reader, XmlAttribute.ID, true), getAttributeValue(reader, XmlAttribute.CLASS, true)); break; default: throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } break; case END_ELEMENT: if (element != XmlElement.BATCH_ARTIFACTS && element != XmlElement.REF) { throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } } } } finally { reader.close(); } return batchArtifacts; } private static Step parseStep(final XMLStreamReader reader, final Job job) throws XMLStreamException { final Step step = new Step(getAttributeValue(reader, XmlAttribute.ID, true)); step.setStartLimit(getAttributeValue(reader, XmlAttribute.START_LIMIT, false)); step.setAllowStartIfComplete(getAttributeValue(reader, XmlAttribute.ALLOW_START_IF_COMPLETE, false)); step.setAttributeNext(getAttributeValue(reader, XmlAttribute.NEXT, false)); step.setAbstract(getAttributeValue(reader, XmlAttribute.ABSTRACT, false)); final String parentVal = getAttributeValue(reader, XmlAttribute.PARENT, false); if (parentVal != null) { step.setParentAndJslName(parentVal, getAttributeValue(reader, XmlAttribute.JSL_NAME, false)); job.inheritingJobElements.add(step); } while (reader.hasNext()) { final int eventType = reader.next(); if (eventType != START_ELEMENT && eventType != END_ELEMENT) { continue; } final XmlElement element = XmlElement.forName(reader.getLocalName()); switch (eventType) { case START_ELEMENT: switch (element) { case PROPERTIES: step.setProperties(parseProperties(reader)); break; case LISTENERS: step.setListeners(parseListeners(reader)); break; case BATCHLET: step.setBatchlet(parseRefArtifact(reader, XmlElement.BATCHLET)); break; case CHUNK: step.setChunk(parseChunk(reader)); break; case PARTITION: step.setPartition(parsePartition(reader)); break; case NEXT: if (step.getAttributeNext() != null) { throw BatchMessages.MESSAGES.cannotHaveBothNextAttributeAndElement(reader.getLocation(), step.getAttributeNext()); } step.addTransitionElement(parseNext(reader)); break; case FAIL: step.addTransitionElement(parseFail(reader)); break; case END: step.addTransitionElement(parseEnd(reader)); break; case STOP: step.addTransitionElement(parseStop(reader)); break; default: throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } break; case END_ELEMENT: switch (element) { case STEP: return step; default: throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } } } throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } private static String getAttributeValue(final XMLStreamReader reader, final XmlAttribute attribute, final boolean required) { final String val = reader.getAttributeValue(namespaceURI, attribute.getLocalName()); if (val == null && required) { throw BatchMessages.MESSAGES.failToGetAttribute(attribute.getLocalName(), reader.getLocation()); } return val; } private static Decision parseDecision(final XMLStreamReader reader) throws XMLStreamException { final Decision decision = new Decision(getAttributeValue(reader, XmlAttribute.ID, true), getAttributeValue(reader, XmlAttribute.REF, true)); while (reader.hasNext()) { final int eventType = reader.next(); if (eventType != START_ELEMENT && eventType != END_ELEMENT) { continue; } final XmlElement element = XmlElement.forName(reader.getLocalName()); switch (eventType) { case START_ELEMENT: switch (element) { case PROPERTIES: decision.setProperties(parseProperties(reader)); break; case NEXT: decision.addTransitionElement(parseNext(reader)); break; case FAIL: decision.addTransitionElement(parseFail(reader)); break; case END: decision.addTransitionElement(parseEnd(reader)); break; case STOP: decision.addTransitionElement(parseStop(reader)); break; default: throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } break; case END_ELEMENT: switch (element) { case DECISION: return decision; default: throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } } } throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } private static Flow parseFlow(final XMLStreamReader reader, final Job job) throws XMLStreamException { final Flow flow = new Flow(getAttributeValue(reader, XmlAttribute.ID, true)); flow.next = getAttributeValue(reader, XmlAttribute.NEXT, false); flow.setAbstract(getAttributeValue(reader, XmlAttribute.ABSTRACT, false)); final String parentVal = getAttributeValue(reader, XmlAttribute.PARENT, false); if (parentVal != null) { flow.setParentAndJslName(parentVal, getAttributeValue(reader, XmlAttribute.JSL_NAME, false)); job.inheritingJobElements.add(flow); } while (reader.hasNext()) { final int eventType = reader.next(); if (eventType != START_ELEMENT && eventType != END_ELEMENT) { continue; } final XmlElement element = XmlElement.forName(reader.getLocalName()); switch (eventType) { case START_ELEMENT: switch (element) { case DECISION: flow.jobElements.add(parseDecision(reader)); break; case FLOW: flow.jobElements.add(parseFlow(reader, job)); break; case SPLIT: flow.jobElements.add(parseSplit(reader, job)); break; case STEP: flow.jobElements.add(parseStep(reader, job)); break; case NEXT: if (flow.getAttributeNext() != null) { throw BatchMessages.MESSAGES.cannotHaveBothNextAttributeAndElement(reader.getLocation(), flow.getAttributeNext()); } flow.addTransitionElement(parseNext(reader)); break; case FAIL: flow.addTransitionElement(parseFail(reader)); break; case END: flow.addTransitionElement(parseEnd(reader)); break; case STOP: flow.addTransitionElement(parseStop(reader)); break; default: throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } break; case END_ELEMENT: switch (element) { case FLOW: return flow; default: throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } } } throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } private static Split parseSplit(final XMLStreamReader reader, final Job job) throws XMLStreamException { final Split split = new Split(getAttributeValue(reader, XmlAttribute.ID, true)); split.setAttributeNext(getAttributeValue(reader, XmlAttribute.NEXT, false)); while (reader.hasNext()) { final int eventType = reader.next(); if (eventType != START_ELEMENT && eventType != END_ELEMENT) { continue; } final XmlElement element = XmlElement.forName(reader.getLocalName()); switch (eventType) { case START_ELEMENT: if (element == XmlElement.FLOW) { split.addFlow(parseFlow(reader, job)); } else { throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } break; case END_ELEMENT: if (element == XmlElement.SPLIT) { return split; } else { throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } } } throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } private static Properties parseProperties(final XMLStreamReader reader) throws XMLStreamException { final Properties properties = new Properties(); properties.setPartition(getAttributeValue(reader, XmlAttribute.PARTITION, false)); properties.setMerge(getAttributeValue(reader, XmlAttribute.MERGE, false)); while (reader.hasNext()) { final int eventType = reader.next(); if (eventType != START_ELEMENT && eventType != END_ELEMENT) { continue; } final XmlElement element = XmlElement.forName(reader.getLocalName()); switch (eventType) { case START_ELEMENT: if (element == XmlElement.PROPERTY) { properties.add(getAttributeValue(reader, XmlAttribute.NAME, true), getAttributeValue(reader, XmlAttribute.VALUE, false)); } else { throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } break; case END_ELEMENT: if (element == XmlElement.PROPERTIES) { return properties; } else if (element != XmlElement.PROPERTY) { throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } } } throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } private static Listeners parseListeners(final XMLStreamReader reader) throws XMLStreamException { final Listeners listeners = new Listeners(); final List<RefArtifact> listenerList = listeners.getListeners(); listeners.setMerge(getAttributeValue(reader, XmlAttribute.MERGE, false)); while (reader.hasNext()) { final int eventType = reader.next(); if (eventType != START_ELEMENT && eventType != END_ELEMENT) { continue; } final XmlElement element = XmlElement.forName(reader.getLocalName()); switch (eventType) { case START_ELEMENT: if (element == XmlElement.LISTENER) { listenerList.add(parseRefArtifact(reader, element)); } else { throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } break; case END_ELEMENT: if (element == XmlElement.LISTENERS) { return listeners; } else { throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } } } throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } private static RefArtifact parseRefArtifact(final XMLStreamReader reader, final XmlElement artifactElementType) throws XMLStreamException { final RefArtifact refArtifact = new RefArtifact(getAttributeValue(reader, XmlAttribute.REF, false)); while (reader.hasNext()) { final int eventType = reader.next(); if (eventType != START_ELEMENT && eventType != END_ELEMENT) { continue; } final XmlElement element = XmlElement.forName(reader.getLocalName()); switch (eventType) { case START_ELEMENT: if (element == XmlElement.PROPERTIES) { refArtifact.setProperties(parseProperties(reader)); } else if(element == XmlElement.SCRIPT) { refArtifact.setScript(parseScript(reader)); } else { throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } break; case END_ELEMENT: if (element == artifactElementType) { return refArtifact; } else { throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } } } throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } private static Script parseScript(final XMLStreamReader reader) throws XMLStreamException { final Script script = new Script(getAttributeValue(reader, XmlAttribute.TYPE, false), getAttributeValue(reader, XmlAttribute.SRC, false)); StringBuilder charactersBuilder = new StringBuilder(); StringBuilder scriptBuilder = new StringBuilder(); while (reader.hasNext()) { final int eventType = reader.next(); switch (eventType) { case CHARACTERS: charactersBuilder.append(reader.getText()); break; case CDATA: scriptBuilder.append(reader.getText()); break; case END_ELEMENT: if (XmlElement.forName(reader.getLocalName()) == XmlElement.SCRIPT) { if (scriptBuilder.length() == 0) { String characters = charactersBuilder.toString().trim(); if(!characters.isEmpty()) { script.content = characters; } } else { script.content = scriptBuilder.toString(); } return script; } else { throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } } } throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } private static Chunk parseChunk(final XMLStreamReader reader) throws XMLStreamException { final Chunk chunk = new Chunk(); chunk.setCheckpointPolicy(getAttributeValue(reader, XmlAttribute.CHECKPOINT_POLICY, false)); chunk.setItemCount(getAttributeValue(reader, XmlAttribute.ITEM_COUNT, false)); chunk.setTimeLimit(getAttributeValue(reader, XmlAttribute.TIME_LIMIT, false)); chunk.setSkipLimit(getAttributeValue(reader, XmlAttribute.SKIP_LIMIT, false)); chunk.setRetryLimit(getAttributeValue(reader, XmlAttribute.RETRY_LIMIT, false)); while (reader.hasNext()) { final int eventType = reader.next(); if (eventType != START_ELEMENT && eventType != END_ELEMENT) { continue; } final XmlElement element = XmlElement.forName(reader.getLocalName()); switch (eventType) { case START_ELEMENT: switch (element) { case READER: chunk.setReader(parseRefArtifact(reader, XmlElement.READER)); break; case PROCESSOR: chunk.setProcessor(parseRefArtifact(reader, XmlElement.PROCESSOR)); break; case WRITER: chunk.setWriter(parseRefArtifact(reader, XmlElement.WRITER)); break; case CHECKPOINT_ALGORITHM: chunk.setCheckpointAlgorithm(parseRefArtifact(reader, XmlElement.CHECKPOINT_ALGORITHM)); break; case SKIPPABLE_EXCEPTION_CLASSES: chunk.setSkippableExceptionClasses(parseExceptionClassFilter(reader, XmlElement.SKIPPABLE_EXCEPTION_CLASSES)); break; case RETRYABLE_EXCEPTION_CLASSES: chunk.setRetryableExceptionClasses(parseExceptionClassFilter(reader, XmlElement.RETRYABLE_EXCEPTION_CLASSES)); break; case NO_ROLLBACK_EXCEPTION_CLASSES: chunk.setNoRollbackExceptionClasses(parseExceptionClassFilter(reader, XmlElement.NO_ROLLBACK_EXCEPTION_CLASSES)); break; default: throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } break; case END_ELEMENT: if (element == XmlElement.CHUNK) { return chunk; } else { throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } } } throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } private static Partition parsePartition(final XMLStreamReader reader) throws XMLStreamException { final Partition partition = new Partition(); while (reader.hasNext()) { final int eventType = reader.next(); if (eventType != START_ELEMENT && eventType != END_ELEMENT) { continue; } final XmlElement element = XmlElement.forName(reader.getLocalName()); switch (eventType) { case START_ELEMENT: switch (element) { case MAPPER: partition.setMapper(parseRefArtifact(reader, XmlElement.MAPPER)); break; case PLAN: partition.setPlan(parsePartitionPlan(reader)); break; case COLLECTOR: partition.setCollector(parseRefArtifact(reader, XmlElement.COLLECTOR)); break; case ANALYZER: partition.setAnalyzer(parseRefArtifact(reader, XmlElement.ANALYZER)); break; case REDUCER: partition.setReducer(parseRefArtifact(reader, XmlElement.REDUCER)); break; default: throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } break; case END_ELEMENT: if (element == XmlElement.PARTITION) { return partition; } else { throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } } } throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } private static PartitionPlan parsePartitionPlan(final XMLStreamReader reader) throws XMLStreamException { final PartitionPlan partitionPlan = new PartitionPlan(); partitionPlan.setThreads(getAttributeValue(reader, XmlAttribute.THREADS, false)); partitionPlan.setPartitions(getAttributeValue(reader, XmlAttribute.PARTITIONS, false)); while (reader.hasNext()) { final int eventType = reader.next(); if (eventType != START_ELEMENT && eventType != END_ELEMENT) { continue; } final XmlElement element = XmlElement.forName(reader.getLocalName()); switch (eventType) { case START_ELEMENT: if (element == XmlElement.PROPERTIES) { partitionPlan.addProperties(parseProperties(reader)); } else { throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } break; case END_ELEMENT: if (element == XmlElement.PLAN) { return partitionPlan; } else { throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } } } throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } private static Transition.Next parseNext(final XMLStreamReader reader) throws XMLStreamException { final Transition.Next next = new Transition.Next(getAttributeValue(reader, XmlAttribute.ON, true)); next.setTo(getAttributeValue(reader, XmlAttribute.TO, true)); finishTransitionElement(reader, XmlElement.NEXT); return next; } private static Transition.Fail parseFail(final XMLStreamReader reader) throws XMLStreamException { final Transition.Fail fail = new Transition.Fail(getAttributeValue(reader, XmlAttribute.ON, true)); fail.setExitStatus(getAttributeValue(reader, XmlAttribute.EXIT_STATUS, false)); finishTransitionElement(reader, XmlElement.FAIL); return fail; } private static Transition.End parseEnd(final XMLStreamReader reader) throws XMLStreamException { final Transition.End end = new Transition.End(getAttributeValue(reader, XmlAttribute.ON, true)); end.setExitStatus(getAttributeValue(reader, XmlAttribute.EXIT_STATUS, false)); finishTransitionElement(reader, XmlElement.END); return end; } private static Transition.Stop parseStop(final XMLStreamReader reader) throws XMLStreamException { final Transition.Stop stop = new Transition.Stop(getAttributeValue(reader, XmlAttribute.ON, true), getAttributeValue(reader, XmlAttribute.RESTART, false)); stop.setExitStatus(getAttributeValue(reader, XmlAttribute.EXIT_STATUS, false)); finishTransitionElement(reader, XmlElement.STOP); return stop; } private static void finishTransitionElement(final XMLStreamReader reader, final XmlElement transitionElement) throws XMLStreamException { while (reader.hasNext()) { final int eventType = reader.next(); if (eventType != START_ELEMENT && eventType != END_ELEMENT) { continue; } final XmlElement element = XmlElement.forName(reader.getLocalName()); if (eventType == END_ELEMENT && element == transitionElement) { return; } throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } private static ExceptionClassFilter parseExceptionClassFilter(final XMLStreamReader reader, final XmlElement artifactElementType) throws XMLStreamException { final ExceptionClassFilter filter = new ExceptionClassFilter(); filter.setMerge(getAttributeValue(reader, XmlAttribute.MERGE, false)); while (reader.hasNext()) { final int eventType = reader.next(); if (eventType != START_ELEMENT && eventType != END_ELEMENT) { continue; } final XmlElement element = XmlElement.forName(reader.getLocalName()); switch (eventType) { case START_ELEMENT: switch (element) { case INCLUDE: ExceptionClassFilter.addExceptionClassTo(getAttributeValue(reader, XmlAttribute.CLASS, true), filter.include); break; case EXCLUDE: ExceptionClassFilter.addExceptionClassTo(getAttributeValue(reader, XmlAttribute.CLASS, true), filter.exclude); break; default: throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } break; case END_ELEMENT: if (element == artifactElementType) { return filter; } else if (element != XmlElement.INCLUDE && element != XmlElement.EXCLUDE) { throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } } } throw BatchMessages.MESSAGES.unexpectedXmlElement(reader.getLocalName(), reader.getLocation()); } }