Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
Copyright 2005-2013 The Kuali Foundation Licensed under the Educational Community 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.opensource.org/licenses/ecl2.php 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 org.kuali.rice.kew.engine.simulation;
 
 
 import java.util.List;
 import java.util.Set;


A WorkflowEngine implementation which runs simulations. This object is not thread-safe and therefore a new instance needs to be instantiated on every use.

Author(s):
Kuali Rice Team (rice.collab@kuali.org)
 
 public class SimulationEngine extends StandardWorkflowEngine implements SimulationWorkflowEngine {
 
     public SimulationEngine () {
         super();
     }
     public SimulationEngine(RouteNodeService routeNodeServiceRouteHeaderService routeHeaderService,
             ParameterService parameterServiceOrchestrationConfig config) {
         super(routeNodeServicerouteHeaderServiceparameterServiceconfig);
     }
 
     private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(SimulationEngine.class);
 
     private SimulationResults results;
 
     @Override
     public SimulationResults runSimulation(SimulationCriteria criteriathrows Exception {
         try {
             this. = criteria;
             this. = new SimulationResults();
             validateCriteria(criteria);
             process(criteria.getDocumentId(), null);
            return ;
        } finally {
            //nulling out the results & criteria since these really should only be local variables.
            this. = null;
            this. = null;
        }
    }
    @Override
    public void process(String documentIdString nodeInstanceIdthrows InvalidActionTakenExceptionDocumentSimulatedRouteException {
    	RouteContext context = RouteContext.createNewRouteContext();
    	try {
    		if (.isActivateRequests() == null) {
    		    activationContext.setActivateRequests(!.getActionsToTake().isEmpty());
    		} else {
    		    activationContext.setActivateRequests(.isActivateRequests().booleanValue());
    		}
    		context.setActivationContext(activationContext);
    		context.setEngineState(new EngineState());
    		// suppress policy errors when running a simulation for the purposes of display on the route log
    		RequestsNode.setSupressPolicyErrors(context);
    		DocumentRouteHeaderValue document = createSimulationDocument(documentIdcontext);
            document.setInitiatorWorkflowId("simulation");
    		if ( (.isDocumentSimulation()) && ( (document.isProcessed()) || (document.isFinal()) ) ) {
    			.setDocument(document);
    			return;
    		}
    		routeDocumentIfNecessary(documentcontext);
    		.setDocument(document);
    		documentId = document.getDocumentId();
    		
    		// detect if MDC already has docId param (to avoid nuking it below)
    		boolean mdcHadDocId = MDC.get("docId") != null;
    		if (!mdcHadDocId) { MDC.put("docId"documentId); }
    		
    		PerformanceLogger perfLog = new PerformanceLogger(documentId);
    		try {
    		    if ( .isInfoEnabled() ) {
    		        .info("Processing document for Simulation: " + documentId);
    		    }
    			List<RouteNodeInstanceactiveNodeInstances = getRouteNodeService().getActiveNodeInstances(document);
    			List<RouteNodeInstancenodeInstancesToProcess = determineNodeInstancesToProcess(activeNodeInstances.getDestinationNodeName());
    			context.setDocument(document);
    			// TODO set document content
    			context.setEngineState(new EngineState());
    			ProcessContext processContext = new ProcessContext(truenodeInstancesToProcess);
    			while (! nodeInstancesToProcess.isEmpty()) {
    				RouteNodeInstance nodeInstance = (RouteNodeInstance)nodeInstancesToProcess.remove(0);
    				if ( !nodeInstance.isActive() ) {
    					continue;
    				}
    				NodeJotter.jotNodeInstance(context.getDocument(), nodeInstance);
    				context.setNodeInstance(nodeInstance);
    				processContext = processNodeInstance(context);
    				if (!hasReachedCompletion(processContextcontext.getEngineState().getGeneratedRequests(), nodeInstance)) {
    					if (processContext.isComplete()) {
    						if (!processContext.getNextNodeInstances().isEmpty()) {
    							nodeInstancesToProcess.addAll(processContext.getNextNodeInstances());
    						}
    						context.getActivationContext().getSimulatedActionsTaken().addAll(processPotentialActionsTaken(contextdocumentnodeInstance));
    					}
    				} else {
    					context.getActivationContext().getSimulatedActionsTaken().addAll(processPotentialActionsTaken(contextdocumentnodeInstance));
    				}
    			}
    			List simulatedActionRequests = context.getEngineState().getGeneratedRequests();
    			Collections.sort(simulatedActionRequestsnew Utilities.RouteLogActionRequestSorter());
    			.setSimulatedActionRequests(simulatedActionRequests);
            } catch (InvalidActionTakenException e) {
                throw e;
            } catch (Exception e) {
                String errorMsg = "Error running simulation for document " + ((.isDocumentSimulation()) ? "id " + documentId.toString() : "type " + .getDocumentTypeName());
                .error(errorMsg,e);
                throw new DocumentSimulatedRouteException(errorMsge);
    		} finally {
    			perfLog.log("Time to run simulation.");
    			RouteContext.clearCurrentRouteContext();
    			
    			if (!mdcHadDocId) { MDC.remove("docID"); }
    		}
    	} finally {
    		RouteContext.releaseCurrentRouteContext();
    	}
    }

    
If there are multiple paths, we need to figure out which ones we need to follow for blanket approval. This method will throw an exception if a node with the given name could not be located in the routing path. This method is written in such a way that it should be impossible for there to be an infinate loop, even if there is extensive looping in the node graph.
        if (org.apache.commons.lang.StringUtils.isEmpty(nodeName)) {
            return activeNodeInstances;
        }
        List<RouteNodeInstancenodeInstancesToProcess = new ArrayList<RouteNodeInstance>();
        for (RouteNodeInstance nodeInstance : activeNodeInstances) {
            if (nodeName.equals(nodeInstance.getName())) {
                // one of active node instances is node instance to stop at
                return new ArrayList<RouteNodeInstance>();
            } else {
                if (isNodeNameInPath(nodeNamenodeInstance)) {
                    nodeInstancesToProcess.add(nodeInstance);
                }
            }
        }
        if (nodeInstancesToProcess.size() == 0) {
            throw new InvalidActionTakenException("Could not locate a node with the given name in the blanket approval path '" + nodeName + "'.  " +
                    "The document is probably already passed the specified node or does not contain the node.");
        }
        return nodeInstancesToProcess;
    }
    private boolean isNodeNameInPath(String nodeNameRouteNodeInstance nodeInstance) {
        boolean isInPath = false;
        for (Iterator<RouteNodeiterator = nodeInstance.getRouteNode().getNextNodes().iterator(); iterator.hasNext();) {
            RouteNode nextNode = (RouteNodeiterator.next();
            isInPath = isInPath || isNodeNameInPath(nodeNamenextNodenew HashSet<String>());
        }
        return isInPath;
    }
    private boolean isNodeNameInPath(String nodeNameRouteNode nodeSet<Stringinspected) {
        boolean isInPath = !inspected.contains(node.getRouteNodeId()) && node.getRouteNodeName().equals(nodeName);
        inspected.add(node.getRouteNodeId());
        if (.isSubProcessNode(node)) {
            ProcessDefinitionBo subProcess = node.getDocumentType().getNamedProcess(node.getRouteNodeName());
            RouteNode subNode = subProcess.getInitialRouteNode();
            if (subNode != null) {
                isInPath = isInPath || isNodeNameInPath(nodeNamesubNodeinspected);
            }
        }
        for (Iterator<RouteNodeiterator = node.getNextNodes().iterator(); iterator.hasNext();) {
            RouteNode nextNode = (RouteNodeiterator.next();
            isInPath = isInPath || isNodeNameInPath(nodeNamenextNodeinspected);
        }
        return isInPath;
    }
    private boolean hasReachedCompletion(ProcessContext processContextList actionRequestsRouteNodeInstance nodeInstanceSimulationCriteria criteria) {
        if (!criteria.getDestinationRecipients().isEmpty()) {
            for (Iterator iterator = actionRequests.iterator(); iterator.hasNext();) {
                ActionRequestValue request = (ActionRequestValueiterator.next();
                for (Iterator<RecipientuserIt = criteria.getDestinationRecipients().iterator(); userIt.hasNext();) {
                    Recipient recipient = (RecipientuserIt.next();
                    if (request.isRecipientRoutedRequest(recipient)) {
                        if ( (org.apache.commons.lang.StringUtils.isEmpty(criteria.getDestinationNodeName())) || (criteria.getDestinationNodeName().equals(request.getNodeInstance().getName())) ) {
                            return true;
                        }
                    }
                }
            }
        }
        return (org.apache.commons.lang.StringUtils.isEmpty(criteria.getDestinationNodeName()) && processContext.isComplete() && processContext.getNextNodeInstances().isEmpty())
            || nodeInstance.getRouteNode().getRouteNodeName().equals(criteria.getDestinationNodeName());
    }
    private List<ActionTakenValueprocessPotentialActionsTaken(RouteContext routeContextDocumentRouteHeaderValue routeHeaderRouteNodeInstance justProcessedNodeSimulationCriteria criteria) {
    	List<ActionTakenValueactionsTaken = new ArrayList<ActionTakenValue>();
    	List requestsToCheck = new ArrayList();
    	requestsToCheck.addAll(routeContext.getEngineState().getGeneratedRequests());
        requestsToCheck.addAll(routeHeader.getActionRequests());
    	List<ActionRequestValuependingActionRequestValues = getCriteriaActionsToDoByNodeName(requestsToCheckjustProcessedNode.getName());
        List<ActionTakenValueactionsToTakeForNode = generateActionsToTakeForNode(justProcessedNode.getName(), routeHeadercriteriapendingActionRequestValues);
        for (ActionTakenValue actionTaken : actionsToTakeForNode)
        {
            KEWServiceLocator.getActionRequestService().deactivateRequests(actionTakenpendingActionRequestValuesrouteContext.getActivationContext());
            actionsTaken.add(actionTaken);
//            routeContext.getActivationContext().getSimulatedActionsTaken().add(actionTaken);
        }
    	return actionsTaken;
    }
    private List<ActionTakenValuegenerateActionsToTakeForNode(String nodeNameDocumentRouteHeaderValue routeHeaderSimulationCriteria criteriaList<ActionRequestValuependingActionRequests) {
        List<ActionTakenValueactions = new ArrayList<ActionTakenValue>();
        if ( (criteria.getActionsToTake() != null) && (!criteria.getActionsToTake().isEmpty()) ) {
            for (SimulationActionToTake simAction : criteria.getActionsToTake()) {
                if (nodeName.equals(simAction.getNodeName())) {
                    actions.add(createDummyActionTaken(routeHeadersimAction.getUser(), simAction.getActionToPerform(), findDelegatorForActionRequests(pendingActionRequests)));
                }
            }
        }
        return actions;
    }
    private List<ActionRequestValuegetCriteriaActionsToDoByNodeName(List generatedRequestsString nodeName) {
    	List<ActionRequestValuerequests = new ArrayList<ActionRequestValue>();
        for (Iterator iterator = generatedRequests.iterator(); iterator.hasNext();) {
            ActionRequestValue request = (ActionRequestValueiterator.next();
            if ( (request.isPending()) && request.getNodeInstance() != null && nodeName.equals(request.getNodeInstance().getName())) {
            	requests.add(request);
            }
        }
        return requests;
    }
    private void validateCriteria(SimulationCriteria criteria) {
    	if (criteria.getDocumentId() == null && org.apache.commons.lang.StringUtils.isEmpty(criteria.getDocumentTypeName())) {
		throw new IllegalArgumentException("No document type name or document id given, cannot simulate a document without a document type name or a document id.");
    	}
    	if (criteria.getXmlContent() == null) {
    		criteria.setXmlContent("");
    	}
    }

    
Creates the document to run the simulation against by loading it from the database or creating a fake document for simulation purposes depending on the passed simulation criteria. If the documentId is available, we load the document from the database, otherwise we create one based on the given DocumentType and xml content.
    private DocumentRouteHeaderValue createSimulationDocument(String documentIdSimulationCriteria criteriaRouteContext context) {
    	DocumentRouteHeaderValue document = null;
    	if (criteria.isDocumentSimulation()) {
            document = getDocumentForSimulation(documentId);
            if (!org.apache.commons.lang.StringUtils.isEmpty(criteria.getXmlContent())) {
                document.setDocContent(criteria.getXmlContent());
            }
    	} else if (criteria.isDocumentTypeSimulation()) {
        	DocumentType documentType = KEWServiceLocator.getDocumentTypeService().findByName(criteria.getDocumentTypeName());
        	if (documentType == null) {
        		throw new IllegalArgumentException("Specified document type could not be found for name '"+criteria.getDocumentTypeName()+"'");
        	}
        	documentId = context.getEngineState().getNextSimulationId().toString();
        	document = new DocumentRouteHeaderValue();
        	context.setDocument(document);
        	document.setDocumentId(documentId);
        	document.setCreateDate(new Timestamp(System.currentTimeMillis()));
        	document.setDocContent(criteria.getXmlContent());
        	document.setDocRouteLevel(new Integer(0));
        	document.setDocumentTypeId(documentType.getDocumentTypeId());
    		initializeDocument(document);
        }
        if (document == null) {
        	throw new IllegalArgumentException("Workflow simulation engine could not locate document with id "+documentId);
        }
        for (ActionRequestValue actionRequest : document.getActionRequests()) {
        	actionRequest = (ActionRequestValuedeepCopy(actionRequest);
        	document.getSimulatedActionRequests().add(actionRequest);
        	for (ActionItem actionItem : actionRequest.getActionItems()) {
        		actionRequest.getSimulatedActionItems().add((ActionItemdeepCopy(actionItem));
        	}
        }
        context.setDocument(document);
        installSimulationNodeInstances(contextcriteria);
		return document;
    }
        DocumentRouteHeaderValue document = getRouteHeaderService().getRouteHeader(documentId);
        return (DocumentRouteHeaderValue)deepCopy(document);
    }
    private Serializable deepCopy(Serializable src) {
        Serializable obj = null;
        if (src != null) {
            ObjectOutputStream oos = null;
            ObjectInputStream ois = null;
            try {
                ByteArrayOutputStream serializer = new ByteArrayOutputStream();
                oos = new ObjectOutputStream(serializer);
                oos.writeObject(src);
                ByteArrayInputStream deserializer = new ByteArrayInputStream(serializer.toByteArray());
                ois = new ObjectInputStream(deserializer);
                obj = (Serializableois.readObject();
            }
            catch (IOException e) {
                throw new RuntimeException("unable to complete deepCopy from src '" + src.toString() + "'"e);
            }
            catch (ClassNotFoundException e) {
                throw new RuntimeException("unable to complete deepCopy from src '" + src.toString() + "'"e);
            }
            finally {
                try {
                    if (oos != null) {
                        oos.close();
                    }
                    if (ois != null) {
                        ois.close();
                    }
                }
                catch (IOException e) {
                    // ignoring this IOException, since the streams are going to be abandoned now anyway
                }
            }
        }
        return obj;
    }
    private void routeDocumentIfNecessary(DocumentRouteHeaderValue documentSimulationCriteria criteriaRouteContext routeContextthrows InvalidActionTakenException {
    	if (criteria.getRoutingUser() != null) {
            ActionTakenValue action = createDummyActionTaken(documentcriteria.getRoutingUser(), .null);
    		routeContext.getActivationContext().getSimulatedActionsTaken().add(action);
            simulateDocumentRoute(actiondocumentcriteria.getRoutingUser(), routeContext);
    	}
    }

    
Looks at the rule templates and/or the startNodeName and creates the appropriate node instances to run simulation against. After creating the node instances, it hooks them all together and installs a "terminal" simulation node to stop the simulation node at the end of the simulation.
    private void installSimulationNodeInstances(RouteContext contextSimulationCriteria criteria) {
    	DocumentRouteHeaderValue document = context.getDocument();
    	List<RouteNodesimulationNodes = new ArrayList<RouteNode>();
    	if (!criteria.getNodeNames().isEmpty()) {
    		for (String nodeName : criteria.getNodeNames()) {
				if ( .isDebugEnabled() ) {
				    .debug("Installing simulation starting node '"+nodeName+"'");
				}
	    		List<RouteNodenodes = KEWServiceLocator.getRouteNodeService().getFlattenedNodes(document.getDocumentType(), true);
	    		boolean foundNode = false;
	    		for (RouteNode node : nodes) {
					if (node.getRouteNodeName().equals(nodeName)) {
						simulationNodes.add(node);
						foundNode = true;
						break;
					}
				}
	    		if (!foundNode) {
	    			throw new IllegalArgumentException("Could not find node on the document type for the given name '"+nodeName+"'");
	    		}
    		}
    	} else if (!criteria.getRuleTemplateNames().isEmpty()) {
    		List<RouteNodenodes = KEWServiceLocator.getRouteNodeService().getFlattenedNodes(document.getDocumentType(), true);
    		for (String ruleTemplateName : criteria.getRuleTemplateNames()) {
				boolean foundNode = false;
				for (RouteNode node : nodes) {
					String routeMethodName = node.getRouteMethodName();
					if (node.isFlexRM() && ruleTemplateName.equals(routeMethodName)) {
						simulationNodes.add(node);
						foundNode = true;
						break;
					}
				}
				if (!foundNode) {
	    			throw new IllegalArgumentException("Could not find node on the document type with the given rule template name '"+ruleTemplateName+"'");
	    		}
			}
    	} else if (criteria.isFlattenNodes()) {
    		// if they want to flatten the nodes, we will essentially process all simple nodes that are defined on the DocumentType
            List<RouteNodenodes = KEWServiceLocator.getRouteNodeService().getFlattenedNodes(document.getDocumentType(), true);
            for ( RouteNode node : nodes ) {
                try {
	                if ( NodeType.fromNodenode ).isTypeOfSimpleNode.class ) 
	                		&& !NodeType.fromNodenode ).isTypeOfNoOpNode.class ) ) {
	                    simulationNodes.add(node);
	                }
                } catch (ResourceUnavailableException ex) {
					.warn"Unable to determine node type in simulator: " + ex.getMessage() );
				}
            }
    	} else {
    	    // in this case, we want to let the document proceed from it's current active node
    		return;
    	}
    	
    	// hook all of the simulation nodes together
    	Branch defaultBranch = document.getInitialRouteNodeInstances().get(0).getBranch();
    	// clear out the initial route node instances, we are going to build a new node path based on what we want to simulate
    	document.getInitialRouteNodeInstances().clear();
    	RouteNodeInstance currentNodeInstance = null;//initialNodeInstance;
    	for (RouteNode simulationNode : simulationNodes) {
			RouteNodeInstance nodeInstance = .getNodeFactory().createRouteNodeInstance(document.getDocumentId(), simulationNode);
			nodeInstance.setBranch(defaultBranch);
			if (currentNodeInstance == null) {
				document.getInitialRouteNodeInstances().add(nodeInstance);
				nodeInstance.setActive(true);
				saveNode(contextnodeInstance);
else {
				currentNodeInstance.addNextNodeInstance(nodeInstance);
				saveNode(contextcurrentNodeInstance);
			}
			currentNodeInstance = nodeInstance;
		}
    	installSimulationTerminationNode(contextdocument.getDocumentType(), currentNodeInstance);
    }
    private void installSimulationTerminationNode(RouteContext contextDocumentType documentTypeRouteNodeInstance lastNodeInstance) {
    	RouteNode terminationNode = new RouteNode();
    	terminationNode.setDocumentType(documentType);
    	terminationNode.setDocumentTypeId(documentType.getDocumentTypeId());
    	terminationNode.setNodeType(NoOpNode.class.getName());
    	terminationNode.setRouteNodeName("SIMULATION_TERMINATION_NODE");
    	RouteNodeInstance terminationNodeInstance = .getNodeFactory().createRouteNodeInstance(lastNodeInstance.getDocumentId(), terminationNode);
    	terminationNodeInstance.setBranch(lastNodeInstance.getBranch());
    	lastNodeInstance.addNextNodeInstance(terminationNodeInstance);
    	saveNode(contextlastNodeInstance);
    }
    // below is pretty much a copy of RouteDocumentAction... but actions have to be faked for now
    private void simulateDocumentRoute(ActionTakenValue actionTakenDocumentRouteHeaderValue documentPerson userRouteContext routeContextthrows InvalidActionTakenException {
        if (document.isRouted()) {
            throw new WorkflowRuntimeException("Document can not simulate a route if it has already been routed");
        }
    	ActionRequestService actionRequestService = KEWServiceLocator.getActionRequestService();
        // TODO delyea - deep copy below
        List<ActionRequestValueactionRequests = new ArrayList<ActionRequestValue>();
        for (Iterator iter = actionRequestService.findPendingByDoc(document.getDocumentId()).iterator(); iter.hasNext();) {
            ActionRequestValue arv = (ActionRequestValuedeepCopy( (ActionRequestValueiter.next() );
            for (ActionItem actionItem : arv.getActionItems()) {
        		arv.getSimulatedActionItems().add((ActionItemdeepCopy(actionItem));
        	}
            actionRequests.add(arv);//(ActionRequestValue)deepCopy(arv));
        }
//        actionRequests.addAll(actionRequestService.findPendingByDoc(document.getDocumentId()));
        .debug("Simulate Deactivating all pending action requests");
        // deactivate any requests for the user that routed the document.
        for (Iterator<ActionRequestValueiter = actionRequests.iterator(); iter.hasNext();) {
            ActionRequestValue actionRequest = (ActionRequestValueiter.next();
            // requests generated to the user who is routing the document should be deactivated
            if ( (user.getPrincipalId().equals(actionRequest.getPrincipalId())) && (actionRequest.isActive()) ) {
            	actionRequestService.deactivateRequest(actionTakenactionRequestrouteContext.getActivationContext());
            }
            // requests generated by a save action should be deactivated
            else if (..equals(actionRequest.getResponsibilityId())) {
            	actionRequestService.deactivateRequest(actionTakenactionRequestrouteContext.getActivationContext());
            }
        }
//        String oldStatus = document.getDocRouteStatus();
        document.markDocumentEnroute();
//        String newStatus = document.getDocRouteStatus();
//        notifyStatusChange(newStatus, oldStatus);
//        getRouteHeaderService().saveRouteHeader(document);
    }
    private ActionTakenValue createDummyActionTaken(DocumentRouteHeaderValue routeHeaderPerson userToPerformActionString actionToPerformRecipient delegator) {
        ActionTakenValue val = new ActionTakenValue();
        val.setActionTaken(actionToPerform);
        if (..equals(actionToPerform)) {
        }
		val.setDocVersion(routeHeader.getDocVersion());
		val.setDocumentId(routeHeader.getDocumentId());
		val.setPrincipalId(userToPerformAction.getPrincipalId());
		if (delegator != null) {
			if (delegator instanceof KimPrincipalRecipient) {
else if (delegator instanceof KimGroupRecipient) {
				Group group = ((KimGroupRecipientdelegator).getGroup();
else{
				throw new IllegalArgumentException("Invalid Recipient type received: " + delegator.getClass().getName());
			}
		}
		//val.setRouteHeader(routeHeader);
		return val;
    }

Used by actions taken Returns the highest priority delegator in the list of action requests.
		return KEWServiceLocator.getActionRequestService().findDelegator(actionRequests);
	}

    
Executes a "saveNode" for the simulation engine, this does not actually save the document, but rather assigns it some simulation ids. Resolves KULRICE-368
    @Override
    protected void saveNode(RouteContext contextRouteNodeInstance nodeInstance) {
		// we shold be in simulation mode here
    	if (nodeInstance.getRouteNodeInstanceId() == null) {
    	}
    	
    	// if we are in simulation mode, lets go ahead and assign some id
    	// values to our beans
    	for (Iterator<RouteNodeInstanceiterator = nodeInstance.getNextNodeInstances().iterator(); iterator.hasNext();) {
    		RouteNodeInstance routeNodeInstance = (RouteNodeInstanceiterator.next();
    		if (routeNodeInstance.getRouteNodeInstanceId() == null) {
    			routeNodeInstance.setRouteNodeInstanceId(context.getEngineState().getNextSimulationId());
    		}
    	}
    	if (nodeInstance.getProcess() != null && nodeInstance.getProcess().getRouteNodeInstanceId() == null) {
    	}
    	if (nodeInstance.getBranch() != null && nodeInstance.getBranch().getBranchId() == null) {
    		nodeInstance.getBranch().setBranchId(context.getEngineState().getNextSimulationId());
    	}
    }
New to GrepCode? Check out our FAQ X