Java tutorial
/* * JBoss, Home of Professional Open Source * Copyright 2005, JBoss Inc., and individual contributors as indicated * by the @authors tag. See the copyright.txt in the distribution for a * full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jbpm.jpdl.internal.convert; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.dom4j.Document; import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.jbpm.jpdl.internal.convert.action.Action; import org.jbpm.jpdl.internal.convert.action.ActionConverterTypes; import org.jbpm.jpdl.internal.convert.action.CreateTimerAction; import org.jbpm.jpdl.internal.convert.exception.ConvertException; import org.jbpm.jpdl.internal.convert.node.Node; import org.jbpm.jpdl.internal.convert.node.NodeConverterTypes; import org.jbpm.jpdl.internal.convert.node.StartState; import org.jbpm.jpdl.internal.convert.node.TaskNode; import org.jbpm.jpdl.internal.convert.node.VariableAccess; import org.jbpm.jpdl.internal.convert.problem.Problem; import org.jbpm.jpdl.internal.convert.problem.ProblemListener; import org.jbpm.jpdl.internal.xml.JpdlParser; import org.jbpm.pvm.internal.util.CollectionUtil; import org.xml.sax.InputSource; /** * Trimmed down version JpdlXmlReader (jBPM3 code base), * to have exactly the same parsing as in jBPM3. * * Note that the foremost requirement for this class is readability and * extensibility. People who want to adapt the conversion logic * for their own processes are advised to adapt this class. * * @author Jim Ma * @author jbarrez */ public class Jpdl3Converter implements ProblemListener { private static final long serialVersionUID = 1L; protected InputSource inputSource; protected Document jpdl3Document; /* Contains any problems occurred after conversion of the jpdl3 process */ protected List<Problem> problems = new ArrayList<Problem>(); /* * Use the constructor with a custom ProblemListener to have the conversion * problems sent to another problem listener/ */ protected ProblemListener problemListener; //the return converted jpdl4 dom model protected Document jpdl4Document; /* Contains the transition destinations which arent resolved yet */ protected Collection<Object[]> unresolvedTransitionDestinations; /* * Contains the actions which arent resolved yet * (ie the converter hasnt encountered them yet) */ protected Collection<Object[]> unresolvedActionReferences; /* Contains the nodes of the process */ protected Map<String, Element> nodeCollection = new java.util.concurrent.ConcurrentHashMap<String, Element>(); /* Contains the encountered swimlanes */ protected Map<String, Element> swimlanesCollection = new java.util.concurrent.ConcurrentHashMap<String, Element>(); /* Used for autonumbering anonymous timers */ private int timerNumber; /** * Constructor using an URL pointing to a jpdl3 process file. */ public Jpdl3Converter(URL url) { try { File file = new File(url.toURI()); FileReader reader = new FileReader(file); this.inputSource = new InputSource(reader); } catch (Exception e) { //Do nothing , we validated it before } } /** * Constructor using an InputSource pointing to a jpdl3 process file. */ public Jpdl3Converter(InputSource inputSource) { this.inputSource = inputSource; } /** * Constructor using a Reader pointing to a jpdl3 process file. */ public Jpdl3Converter(Reader reader) { this(new InputSource(reader)); } public void close() throws IOException { InputStream byteStream = inputSource.getByteStream(); if (byteStream != null) { byteStream.close(); } else { Reader charStream = inputSource.getCharacterStream(); if (charStream != null) { charStream.close(); } } jpdl3Document = null; } public void addProblem(Problem problem) { problems.add(problem); if (problemListener != null) { problemListener.addProblem(problem); } } public void addError(String description) { log.error("invalid process xml: " + description); addProblem(new Problem(Problem.LEVEL_ERROR, description)); } public void addError(String description, Throwable exception) { log.error("invalid process xml: " + description, exception); addProblem(new Problem(Problem.LEVEL_ERROR, description, exception)); } public void addWarning(String description) { log.warn("process xml warning: " + description); addProblem(new Problem(Problem.LEVEL_WARNING, description)); } /** * Call this method to convert the jpdl3 process. * The return value will be a converted jpdl4 XML model. */ public Document readAndConvert() { // create a new definition jpdl4Document = DocumentHelper.createDocument(); // initialize lists problems = new ArrayList<Problem>(); unresolvedTransitionDestinations = new ArrayList<Object[]>(); unresolvedActionReferences = new ArrayList<Object[]>(); try { // parse the document into a XML dom tree jpdl3Document = Jpdl3ConverterParser.parse(inputSource, this); jpdl4Document.setXMLEncoding(jpdl3Document.getXMLEncoding()); // Convert the root element Element jpdl3Root = jpdl3Document.getRootElement(); Element jpdl4Root = parseProcessDefinitionAttributes(jpdl3Root); // convert process description as comment String description = jpdl3Root.elementTextTrim("description"); if (description != null) { jpdl4Root.addComment(description); } // first pass convertSwimlanes(jpdl3Root, jpdl4Root); convertActions(jpdl3Root, null, null); // Todo: refactor convertNodes(jpdl3Root, jpdl4Root); convertEvents(jpdl3Root, jpdl4Root); convertExceptionHandlers(jpdl3Root, jpdl4Root); convertTasks(jpdl3Root, jpdl4Root); // second pass processing: process any unresolved elements resolveTransitionDestinations(); //resolveActionReferences(); // not yet implemented verifySwimlaneAssignments(); } catch (Exception e) { log.error("couldn't parse process definition", e); addProblem(new Problem(Problem.LEVEL_ERROR, "couldn't parse process definition", e)); } // After conversion: check if there were any conversion problems if (Problem.containsProblemsOfLevel(problems, Problem.LEVEL_ERROR)) { throw new ConvertException(problems); } if (problems != null) { for (Problem problem : problems) { log.warn("process parse warning: " + problem.getDescription()); } } return jpdl4Document; } protected Element parseProcessDefinitionAttributes(Element root) { Element element = jpdl4Document.addElement("process", JpdlParser.CURRENT_VERSION_NAMESPACE); element.addAttribute("name", root.attributeValue("name")); return element; } protected void convertSwimlanes(Element jpdl3Element, Element jpdl4Element) { Iterator<?> iter = jpdl3Element.elementIterator("swimlane"); while (iter.hasNext()) { Element swimlaneElement = (Element) iter.next(); String swimlaneName = swimlaneElement.attributeValue("name"); if (swimlaneName == null) { addWarning("there's a swimlane without a name"); } else { Element swimlane4 = jpdl4Element.addElement("swimlane"); swimlane4.addAttribute("name", swimlaneName); Element assignmentElement = swimlaneElement.element("assignment"); swimlanesCollection.put(swimlaneName, swimlane4); if (assignmentElement != null) { if ((assignmentElement.attribute("actor-id") != null)) { swimlane4.addAttribute("assignee", assignmentElement.attributeValue("actor-id")); } if (assignmentElement.attribute("pooled-actors") != null) { swimlane4.addAttribute("candidate-users", assignmentElement.attributeValue("pooled-actors")); } else { //readAssignmentDelegation(assignmentElement, swimlane4); //swimlane.setAssignmentDelegation(assignmentDelegation); } } } } } public void convertActions(Element jpdl3Element, Element jpdl4Element, String eventType) { Iterator<?> nodeElementIter = jpdl3Element.elementIterator(); while (nodeElementIter.hasNext()) { Element actionElement = (Element) nodeElementIter.next(); String actionName = actionElement.getName(); if ("cancel-timer".equalsIgnoreCase(actionName) || "mail".equalsIgnoreCase(actionName)) { this.addWarning("Unsupported " + actionName + " conversion on Element : " + actionElement.asXML()); } if (ActionConverterTypes.hasActionName(actionName)) { createAction(actionElement, jpdl4Element); } } } public void convertNodes(Element jpdl3Element, Element jpdl4Element) { Iterator<?> nodeElementIter = jpdl3Element.elementIterator(); while (nodeElementIter.hasNext()) { Element nodeElement = (Element) nodeElementIter.next(); String nodeName = nodeElement.getName(); Class<?> nodeType = NodeConverterTypes.getNodeType(nodeName); if (nodeType != null) { Node node = null; try { node = (Node) nodeType.newInstance(); } catch (Exception e) { log.error("couldn't instantiate node '" + nodeName + "', of type '" + nodeType.getName() + "'", e); continue; } //Implement it List<?> starts = jpdl4Element.elements("start-state"); // check for duplicate start-states if ((node instanceof StartState) && !starts.isEmpty()) { addError("max one start-state allowed in a process"); } else if (node instanceof TaskNode) { convertTasks(nodeElement, jpdl4Element); } else { // read the common node parts of the element node.setNodeElement(nodeElement); Element actionElement = nodeElement.element("action"); //The node without action element can not be converted, report error if (nodeName.equals("node") && actionElement == null) { addError("Could not convert a node without action element:" + nodeElement.asXML()); return; } node.createConvertedElement(jpdl4Element); convertNode(nodeElement, node.getConvertedElement()); // if the has special configuration to parse, delegate to the specific node node.read(this); } } } } public void convertEvents(Element jpdl3Element, Element jpdl4Element) { Iterator<?> iter = jpdl3Element.elementIterator("event"); while (iter.hasNext()) { Element eventElement = (Element) iter.next(); //String eventType = eventElement.attributeValue("type"); //TODO:how to handle the event type Element onElement = jpdl4Element.addElement("on"); String type = eventElement.attributeValue("type"); onElement.addAttribute("event", type); convertActions(eventElement, onElement, type); } } public void convertTasks(Element jpdl3Element, Element jpdl4Element) { List<?> elements = jpdl3Element.elements("task"); Element[] tasks = elements.toArray(new Element[0]); if (tasks != null && tasks.length > 0) { for (int i = 0; i < tasks.length; i++) { Element tmpTask = tasks[i]; Element task4 = convertTask(tmpTask, jpdl4Element); if (i == 0) { task4.addAttribute("name", jpdl3Element.attributeValue("name")); } else { task4.addAttribute("name", tmpTask.attributeValue("name")); } // In jBPM4, tasks are not nested in a task node // this is solved by adding seperate task to the jpdl4 model // each containing a transition to the next one if (i + 1 < tasks.length) { Element newTransistion = task4.addElement("transition"); String to = tasks[i + 1].attributeValue("name"); newTransistion.addAttribute("name", to); newTransistion.addAttribute("to", to); } else { //The last task node has a transition to the node after the original TaskNode List<?> transitions = jpdl3Element.elements("transition"); for (Element trans : CollectionUtil.checkList(transitions, Element.class)) { Element transElement = task4.addElement("transition"); String transName = trans.attributeValue("name") == null ? trans.attributeValue("to") : trans.attributeValue("name"); transElement.addAttribute("name", transName); transElement.addAttribute("to", trans.attributeValue("to")); } } } } } public Element convertTask(Element taskElement, Element jpdlElement) { Element task4 = jpdlElement.addElement("task"); String name = taskElement.attributeValue("name"); if (name != null) { task4.attributeValue("name", name); } String description = taskElement.elementTextTrim("description"); if (description != null) { task4.addComment(description); } String condition = taskElement.elementTextTrim("condition"); if (condition == null) { condition = taskElement.attributeValue("condition"); } if (condition == null) { addWarning("Unsupported condition attribute converstion for task : " + taskElement.asXML()); } //The converted the elements in task should be in this sequence //assignment-handler, on, notification, reminder, timer // assignment String swimlaneName = taskElement.attributeValue("swimlane"); Element assignmentElement = taskElement.element("assignment"); // if there is a swimlane attribute specified if (swimlaneName != null) { Element swimLane = swimlanesCollection.get(swimlaneName); if (swimLane == null) { addWarning("task references unknown swimlane '" + swimlaneName + "':" + taskElement.asXML()); } else { task4.addAttribute("swimlane", swimlaneName); } } else if (assignmentElement != null) { if ((assignmentElement.attribute("actor-id") != null) || (assignmentElement.attribute("pooled-actors") != null)) { String actorid = assignmentElement.attributeValue("actor-id"); String pooledactors = assignmentElement.attributeValue("pooled-actors"); if (actorid != null) { task4.addAttribute("assignee", actorid); } if (pooledactors != null) { task4.addAttribute("candidate-groups", pooledactors); } } else { convertAssignmentDelegation(assignmentElement, task4); } } else { // the user has to manage assignment manually, so we better inform him/her. log.info("process xml information: no swimlane or assignment specified for task '" + taskElement.asXML() + "'"); } //notification attribute String notificationsText = taskElement.attributeValue("notify"); if (notificationsText != null && ("true".equalsIgnoreCase(notificationsText) || "on".equalsIgnoreCase(notificationsText) || "yes".equalsIgnoreCase(notificationsText))) { //TODO:Review if there is "continue" attribute to converted Element notify = task4.addElement("notification"); notify.addAttribute("continue", "sync"); } //Reminder elements convertTaskReminders(taskElement, task4); //event elements convertEvents(taskElement, task4); //timer elements convertTaskTimers(taskElement, task4); convertExceptionHandlers(taskElement, task4); String duedateText = taskElement.attributeValue("duedate"); if (duedateText != null) { addWarning("Unsupported duedateDate attribute converstion for task : " + taskElement.asXML()); } String priorityText = taskElement.attributeValue("priority"); if (priorityText != null) { addWarning("Unsupported priorityText attribute converstion for task : " + taskElement.asXML()); } String blockingText = taskElement.attributeValue("blocking"); if (blockingText != null) { addWarning("Unsupported blocking attribute converstion for task : " + taskElement.asXML()); } String signallingText = taskElement.attributeValue("signalling"); if (signallingText != null) { addWarning("Unsupported signallingText attribute converstion for task : " + taskElement.asXML()); } Element taskControllerElement = taskElement.element("controller"); if (taskControllerElement != null) { addWarning("Unsupported controller converstion for task : " + taskElement.asXML()); } return task4; } protected void convertAssignmentDelegation(Element jpdl3AssignmentElement, Element jpdl4Task) { String expression = jpdl3AssignmentElement.attributeValue("expression"); String actorId = jpdl3AssignmentElement.attributeValue("actor-id"); String pooledActors = jpdl3AssignmentElement.attributeValue("pooled-actors"); if (expression != null) { // TODO:How to convert default assignmenthandler? // assignmentDelegation.setClassName("org.jbpm.identity.assignment.ExpressionAssignmentHandler"); // assignmentDelegation.setConfiguration("<expression>" + expression + // "</expression>"); } else if ((actorId != null) || (pooledActors != null)) { jpdl4Task.addComment( "Please Update the AssignmentHandler and implement org.jbpm.api.task.AssignmentHandler to create your own AssignmentHandler."); Element assignmentHandler = jpdl4Task.addElement("assignment-handler"); assignmentHandler.addAttribute("class", "org.jbpm.taskmgmt.assignment.ActorAssignmentHandler"); String configuration = ""; if (actorId != null) { configuration += "<actorId>" + actorId + "</actorId>"; } if (pooledActors != null) { configuration += "<pooledActors>" + pooledActors + "</pooledActors>"; } } else { String claz = jpdl3AssignmentElement.attributeValue("class"); Element assignmentHandler = jpdl4Task.addElement("assignment-handler"); assignmentHandler.addAttribute("class", claz); } } public List<VariableAccess> convertVariableAccesses(Element element) { List<VariableAccess> variableAccesses = new ArrayList<VariableAccess>(); Iterator<?> iter = element.elementIterator("variable"); while (iter.hasNext()) { Element variableElement = (Element) iter.next(); String variableName = variableElement.attributeValue("name"); if (variableName == null) { addProblem(new Problem(Problem.LEVEL_WARNING, "the name attribute of a variable element is required: " + variableElement.asXML())); } String access = variableElement.attributeValue("access", "read,write"); String mappedName = variableElement.attributeValue("mapped-name"); variableAccesses.add(new VariableAccess(variableName, access, mappedName)); } return variableAccesses; } public void convertNode(Element jpdl3Element, Element jpdl4Element) { String name = jpdl3Element.attributeValue("name"); if (name != null) { jpdl4Element.addAttribute("name", name); nodeCollection.put(name, jpdl4Element); } // get the node description String description = jpdl3Element.elementTextTrim("description"); if (description != null) { jpdl4Element.addComment(description); } String asyncText = jpdl3Element.attributeValue("async"); if ("true".equalsIgnoreCase(asyncText)) { jpdl4Element.addAttribute("continue", "async"); } else if ("exclusive".equalsIgnoreCase(asyncText)) { jpdl4Element.addAttribute("continue", "exclusive"); } //else if -> uses the default continue="sync" // parse common subelements convertNodeTimers(jpdl3Element, jpdl4Element); convertEvents(jpdl3Element, jpdl4Element); convertExceptionHandlers(jpdl3Element, jpdl4Element); // save the transitions and parse them at the end addUnresolvedTransitionDestination(jpdl3Element, jpdl4Element); } protected void convertNodeTimers(Element nodeElement, Element jpdl4Element) { Iterator<?> iter = nodeElement.elementIterator("timer"); while (iter.hasNext()) { Element timerElement = (Element) iter.next(); convertNodeTimer(timerElement, jpdl4Element); } } protected void convertNodeTimer(Element timerElement, Element jpdl4Element) { String name = timerElement.attributeValue("name", timerElement.getName()); if (name == null) name = generateTimerName(); CreateTimerAction createTimerAction = new CreateTimerAction(); Element onElement = jpdl4Element.addElement("on"); //TODO: Look at how to map the event type : start , end and take onElement.addAttribute("event", "timeout"); createTimerAction.createConvertedElement(timerElement, onElement); createTimerAction.read(timerElement, this); } private String generateTimerName() { return "timer-" + (timerNumber++); } protected void convertTaskTimers(Element taskElement, Element jdpl4Element) { Iterator<?> iter = taskElement.elementIterator(); while (iter.hasNext()) { Element element = (Element) iter.next(); if (("timer".equals(element.getName()))) { convertTaskTimer(element, jdpl4Element); } } } protected void convertTaskReminders(Element taskElement, Element jdpl4Element) { Iterator<?> iter = taskElement.elementIterator(); while (iter.hasNext()) { Element element = (Element) iter.next(); if ("reminder".equals(element.getName())) { convertTaskTimer(element, jdpl4Element); } } } protected void convertTaskTimer(Element timerElement, Element jpdl4Element) { if ("timer".equals(timerElement.getName())) { String name = timerElement.attributeValue("name", timerElement.getName()); if (name == null) name = generateTimerName(); ; Element timer = jpdl4Element.addElement("timer"); String dueDate = timerElement.attributeValue("duedate"); if (dueDate == null) { addWarning("no duedate specified in create timer action '" + timerElement.asXML() + "'"); } else { timer.addAttribute("duedate", dueDate); } String repeat = timerElement.attributeValue("repeat"); if ("true".equalsIgnoreCase(repeat) || "yes".equalsIgnoreCase(repeat)) { repeat = dueDate; } timer.addAttribute("repeat", repeat); String transitionName = timerElement.attributeValue("transition"); if ((transitionName != null) && (repeat != null)) { repeat = null; addProblem(new Problem(Problem.LEVEL_WARNING, "ignoring repeat on timer with transition " + timerElement.asXML())); } this.convertSingleAction(timerElement, timer); } else { Element reminder = jpdl4Element.addElement("reminder"); String dueDate = timerElement.attributeValue("duedate"); if (dueDate == null) { addWarning("no duedate specified in reminder element '" + timerElement.asXML() + "'"); } else { reminder.addAttribute("duedate", dueDate); } String repeat = timerElement.attributeValue("repeat"); if ("true".equalsIgnoreCase(repeat) || "yes".equalsIgnoreCase(repeat)) { repeat = dueDate; } reminder.addAttribute("repeat", repeat); //TODO: review it if there is no "continue" attribute to converted } } public Element convertSingleAction(Element jpdl3NodeElement, Element jpdl4Element) { Element jpdl4Action = null; Iterator<?> iter = jpdl3NodeElement.elementIterator(); while (iter.hasNext()) { Element candidate = (Element) iter.next(); if (ActionConverterTypes.hasActionName(candidate.getName())) { // parse the action and assign it to this node jpdl4Action = createAction(candidate, jpdl4Element); } } return jpdl4Action; } public Element createAction(Element actionElement, Element jpdl4Element) { // create a new instance of the action Action action = null; String actionName = actionElement.getName(); Class<? extends Action> actionType = ActionConverterTypes.getActionType(actionName); try { action = actionType.newInstance(); } catch (Exception e) { log.error("couldn't instantiate action '" + actionName + "', of type '" + actionType.getName() + "'", e); } Element action4 = action.createConvertedElement(actionElement, jpdl4Element); action.read(actionElement, this); return action4; } protected void convertExceptionHandlers(Element jpdl3Element, Element jpdl4Element) { Iterator<?> iter = jpdl3Element.elementIterator("exception-handler"); if (iter.hasNext()) { addWarning("Unsupported exception handler conversion for element : <" + jpdl3Element.getName() + " name=\"" + jpdl3Element.attributeValue("name") + "\"/>"); } } // transition destinations are parsed in a second pass ////////////////////// public void addUnresolvedTransitionDestination(Element nodeElement, Element jpdl4Element) { unresolvedTransitionDestinations.add(new Object[] { nodeElement, jpdl4Element }); } public void resolveTransitionDestinations() { for (Object[] unresolvedTransition : unresolvedTransitionDestinations) { Element nodeElement = (Element) unresolvedTransition[0]; Element jpdl4Element = (Element) unresolvedTransition[1]; resolveTransitionDestinations(nodeElement.elements("transition"), jpdl4Element); } } public void resolveTransitionDestinations(List<?> transitionElements, Element jpdl4Element) { for (Object transitionElement : transitionElements) { resolveTransitionDestination((Element) transitionElement, jpdl4Element); } } /* * creates the transition object and configures it by the read attributes * @return the created <code>org.jbpm.graph.def.Transition</code> object (useful, if you want to override this method to read additional configuration properties) */ public void resolveTransitionDestination(Element transitionElement, Element jpdl4Element) { Element transition4 = jpdl4Element.addElement("transition"); transition4.addAttribute("name", transitionElement.attributeValue("name")); if (transitionElement.elementTextTrim("description") != null) { transition4.addComment(transitionElement.elementTextTrim("description")); } //Get condition from jpdl3 element String condition = transitionElement.attributeValue("condition"); if (condition == null) { Element conditionElement = transitionElement.element("condition"); if (conditionElement != null) { condition = conditionElement.getTextTrim(); // for backwards compatibility if ((condition == null) || (condition.length() == 0)) { condition = conditionElement.attributeValue("expression"); } } } if (condition != null && condition.length() > 0) { Element condition4 = transition4.addElement("condition"); condition4.addAttribute("expr", condition); } // set destinationNode of the transition String toName = transitionElement.attributeValue("to"); if (toName == null) { addWarning("node '" + transitionElement.getPath() + "' has a transition without a 'to'-attribute to specify its destinationNode"); } else { Element to = this.findNode(toName); if (to == null) { addWarning("transition to='" + toName + "' on node '" + transitionElement.getName() + "' cannot be resolved"); } transition4.addAttribute("to", toName); } // read the actions convertActions(transitionElement, transition4, ""); convertExceptionHandlers(transitionElement, transition4); } // action references are parsed in a second pass //////////////////////////// public void addUnresolvedActionReference(Element actionElemen) { //unresolvedActionReferences.add(new Object[] { actionElemen}); } public void resolveActionReferences() { } // verify swimlane assignments in second pass /////////////////////////////// public void verifySwimlaneAssignments() { // } // mail delegations ///////////////////////////////////////////////////////// public void createMailDelegation(String template, String actors, String to, String subject, String text) { // } public String getProperty(String property, Element element) { String value = element.attributeValue(property); if (value == null) { Element propertyElement = element.element(property); if (propertyElement != null) { value = propertyElement.getText(); } } return value; } //New added method private Element findNode(String name) { return nodeCollection.get(name); } public Document getJpdl4Document() { return jpdl4Document; } private static final Log log = LogFactory.getLog(Jpdl3Converter.class); }