
org.ow2.bonita.facade.impl.RuntimeAPIImpl Maven / Gradle / Ivy
/**
* Copyright (C) 2006 Bull S. A. S.
* Bull, Rue Jean Jaures, B.P.68, 78340, Les Clayes-sous-Bois
* This library 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
* version 2.1 of the License.
* This library 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
* program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
* Floor, Boston, MA 02110-1301, USA.
**/
package org.ow2.bonita.facade.impl;
import java.util.Date;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.ow2.bonita.definition.Performer;
import org.ow2.bonita.definition.XpdlProcess;
import org.ow2.bonita.facade.def.dataType.BasicTypeDefinition;
import org.ow2.bonita.facade.def.dataType.DataTypeDefinition;
import org.ow2.bonita.facade.def.element.FormalParameterDefinition;
import org.ow2.bonita.facade.def.majorElement.ActivityDefinition;
import org.ow2.bonita.facade.def.majorElement.DataFieldDefinition;
import org.ow2.bonita.facade.def.majorElement.PackageFullDefinition;
import org.ow2.bonita.facade.def.majorElement.ParticipantDefinition;
import org.ow2.bonita.facade.def.majorElement.ProcessFullDefinition;
import org.ow2.bonita.facade.exception.ActivityNotFoundException;
import org.ow2.bonita.facade.exception.BonitaInternalException;
import org.ow2.bonita.facade.exception.BonitaWrapperException;
import org.ow2.bonita.facade.exception.IllegalTaskStateException;
import org.ow2.bonita.facade.exception.InstanceNotFoundException;
import org.ow2.bonita.facade.exception.ProcessNotFoundException;
import org.ow2.bonita.facade.exception.TaskNotFoundException;
import org.ow2.bonita.facade.exception.TypeMismatchException;
import org.ow2.bonita.facade.exception.UncancellableInstanceException;
import org.ow2.bonita.facade.exception.UndeletableInstanceException;
import org.ow2.bonita.facade.exception.VariableNotFoundException;
import org.ow2.bonita.facade.internal.InternalRuntimeAPI;
import org.ow2.bonita.facade.runtime.ActivityBody;
import org.ow2.bonita.facade.runtime.ActivityFullInstance;
import org.ow2.bonita.facade.runtime.ActivityState;
import org.ow2.bonita.facade.runtime.InstanceState;
import org.ow2.bonita.facade.runtime.ProcessFullInstance;
import org.ow2.bonita.facade.runtime.ProcessInstance;
import org.ow2.bonita.facade.runtime.var.Enumeration;
import org.ow2.bonita.facade.uuid.ActivityInstanceUUID;
import org.ow2.bonita.facade.uuid.PackageDefinitionUUID;
import org.ow2.bonita.facade.uuid.ProcessDefinitionUUID;
import org.ow2.bonita.facade.uuid.ProcessInstanceUUID;
import org.ow2.bonita.facade.uuid.TaskUUID;
import org.ow2.bonita.pvm.job.Timer;
import org.ow2.bonita.runtime.TaskRunTime;
import org.ow2.bonita.runtime.XpdlExecution;
import org.ow2.bonita.runtime.XpdlInstance;
import org.ow2.bonita.services.Archiver;
import org.ow2.bonita.services.Querier;
import org.ow2.bonita.services.Recorder;
import org.ow2.bonita.services.Repository;
import org.ow2.bonita.util.BonitaRuntimeException;
import org.ow2.bonita.util.EngineEnvTool;
import org.ow2.bonita.util.Misc;
/**
* @author Marc Blachon, Guillaume Porcher, Charles Souillard, Miguel Valdes, Pierre Vigneras
*/
public class RuntimeAPIImpl implements InternalRuntimeAPI {
private static final Logger LOG = Logger.getLogger(ManagementAPIImpl.class.getName());
protected RuntimeAPIImpl() {
}
/**
* Create an instance of the specified process and return the processUUID
*/
public ProcessInstanceUUID instantiateProcess(final ProcessDefinitionUUID processUUID) throws ProcessNotFoundException {
try {
return instantiateProcess(processUUID, null);
} catch (final VariableNotFoundException e) {
//must never occur
throw new BonitaRuntimeException(e);
}
}
public ProcessInstanceUUID instantiateProcess(final ProcessDefinitionUUID processUUID,
final Map variables) throws ProcessNotFoundException, VariableNotFoundException {
FacadeUtil.checkArgsNotNull(processUUID);
final XpdlProcess process = FacadeUtil.getProcessDefinition(processUUID);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Starting a new instance of process : " + processUUID);
}
final XpdlExecution rootExecution = (XpdlExecution) process.beginProcessInstance();
final ProcessInstanceUUID instanceUUID = rootExecution.getXpdlInstance().getUUID();
if (variables != null && !variables.isEmpty()) {
for (final Map.Entry variable : variables.entrySet()) {
try {
setProcessInstanceVariable(rootExecution.getXpdlInstance(), instanceUUID, variable.getKey(), variable.getValue());
} catch (final InstanceNotFoundException e) {
throw new BonitaRuntimeException(e);
} catch (final RuntimeException e) {
System.err.println("\n*****\n\nINSTANCE UUID=" + instanceUUID);
e.printStackTrace();
throw e;
}
}
}
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Started: " + rootExecution.getXpdlInstance());
}
rootExecution.signal();
return instanceUUID;
}
public void cancelProcessInstance(final ProcessInstanceUUID instanceUUID) throws InstanceNotFoundException,
UncancellableInstanceException {
//if this instance is a child execution, throw an exception
FacadeUtil.checkArgsNotNull(instanceUUID);
final Repository repository = EngineEnvTool.getRepository();
final Querier allQueriers = EngineEnvTool.getAllQueriers();
final ProcessFullInstance processInst = allQueriers.getProcessInstance(instanceUUID);
if (processInst == null) {
throw new InstanceNotFoundException(instanceUUID);
}
final XpdlInstance xpdlInstance = repository.getXpdlInstance(instanceUUID);
final ProcessInstanceUUID parentInstanceUUID = processInst.getParentInstanceUUID();
//if this instance is a child execution, throw an exception
if (xpdlInstance == null
|| parentInstanceUUID != null
|| !xpdlInstance.getInstanceState().equals(InstanceState.STARTED)) {
throw new UncancellableInstanceException(instanceUUID, parentInstanceUUID, processInst.getInstanceState());
}
xpdlInstance.cancel();
}
public void deleteProcessInstance(final ProcessInstanceUUID instanceUUID) throws InstanceNotFoundException,
UndeletableInstanceException {
//if this instance is a child execution, throw an exception
//if this instance has children, delete them
FacadeUtil.checkArgsNotNull(instanceUUID);
final Repository repository = EngineEnvTool.getRepository();
final Querier allQueriers = EngineEnvTool.getAllQueriers();
final Querier journal = EngineEnvTool.getJournalQueriers();
final Querier history = EngineEnvTool.getHistoryQueriers();
ProcessFullInstance processInst = journal.getProcessInstance(instanceUUID);
boolean inHistory = false;
final boolean inJournal = processInst != null;
if (!inJournal) {
processInst = history.getProcessInstance(instanceUUID);
inHistory = processInst != null;
}
if (processInst == null) {
throw new InstanceNotFoundException(instanceUUID);
}
final ProcessInstanceUUID parentInstanceUUID = processInst.getParentInstanceUUID();
//check that the parent instance does not exist anymore, else, throw an exception
if (parentInstanceUUID != null && allQueriers.getProcessInstance(parentInstanceUUID) != null) {
throw new UndeletableInstanceException(instanceUUID, parentInstanceUUID);
}
if (inJournal) {
final Set timers = repository.getInstanceTimers(instanceUUID);
if (timers != null) {
for (final Timer timer : timers) {
repository.removeTimer(timer);
}
}
final Set tasks = repository.getInstanceTasks(instanceUUID);
if (tasks != null) {
for (final TaskRunTime task : tasks) {
repository.removeTask(task);
}
}
repository.removeXpdlInstance(instanceUUID);
final Recorder recorder = EngineEnvTool.getRecorder();
recorder.remove(processInst);
} else if (inHistory) {
final Archiver archiver = EngineEnvTool.getArchiver();
archiver.remove(processInst);
}
final Set children = processInst.getChildrenInstanceUUID();
for (final ProcessInstanceUUID child : children) {
deleteProcessInstance(child);
}
}
public void deleteAllProcessInstances(final ProcessDefinitionUUID processUUID) throws ProcessNotFoundException,
UndeletableInstanceException {
FacadeUtil.checkArgsNotNull(processUUID);
final Querier querier = EngineEnvTool.getAllQueriers();
final ProcessFullDefinition process = querier.getProcess(processUUID);
if (process == null) {
throw new ProcessNotFoundException(processUUID);
}
Set instances = querier.getProcessInstances(processUUID);
for (final ProcessFullInstance instance : instances) {
//deletes only parent instances
if (instance.getParentInstanceUUID() == null) {
try {
deleteProcessInstance(instance.getUUID());
} catch (final InstanceNotFoundException e) {
throw new BonitaInternalException("Invalid state", e);
}
}
}
instances = querier.getProcessInstances(processUUID);
if (instances != null && !instances.isEmpty()) {
final ProcessInstance first = instances.iterator().next();
throw new UndeletableInstanceException(first.getUUID(), first.getParentInstanceUUID());
}
}
public void startTask(final TaskUUID taskUUID, final boolean assignTask) throws
TaskNotFoundException, IllegalTaskStateException {
FacadeUtil.checkArgsNotNull(taskUUID);
final Repository repository = EngineEnvTool.getRepository();
final TaskRunTime taskRunTime = repository.getTask(taskUUID);
if (taskRunTime == null) {
throw new TaskNotFoundException(taskUUID);
}
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Checking compatible state of " + taskRunTime);
}
if (!taskRunTime.getState().equals(ActivityState.READY)) {
final Set expectedStates = new HashSet();
expectedStates.add(ActivityState.READY);
throw new IllegalTaskStateException("Can't start task with taskUUID = ", taskUUID,
expectedStates, taskRunTime.getState());
}
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Starting: " + taskRunTime);
}
taskRunTime.start(EngineEnvTool.getUserId(), assignTask);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Started: " + taskRunTime);
}
}
public void finishTask(final TaskUUID taskUUID, final boolean assignTask) throws TaskNotFoundException,
IllegalTaskStateException {
FacadeUtil.checkArgsNotNull(taskUUID);
final Repository repository = EngineEnvTool.getRepository();
final TaskRunTime taskRunTime = repository.getTask(taskUUID);
if (taskRunTime == null) {
throw new TaskNotFoundException(taskUUID);
}
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Checking compatible state of " + taskRunTime);
}
if (!taskRunTime.getState().equals(ActivityState.EXECUTING)) {
final Set expectedStates = new HashSet();
expectedStates.add(ActivityState.EXECUTING);
throw new IllegalTaskStateException("Can't finish task with taskUUID = ",
taskUUID, expectedStates, taskRunTime.getState());
}
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Terminating: " + taskRunTime);
}
taskRunTime.finish(EngineEnvTool.getUserId(), assignTask);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Terminated: " + taskRunTime);
}
}
public void suspendTask(final TaskUUID taskUUID, final boolean assignTask) throws TaskNotFoundException,
IllegalTaskStateException {
FacadeUtil.checkArgsNotNull(taskUUID);
final Repository repository = EngineEnvTool.getRepository();
final TaskRunTime taskRunTime = repository.getTask(taskUUID);
if (taskRunTime == null) {
throw new TaskNotFoundException(taskUUID);
}
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Checking compatible state of " + taskRunTime);
}
if (!(taskRunTime.getState().equals(ActivityState.READY)
|| taskRunTime.getState().equals(ActivityState.EXECUTING))) {
final Set expectedStates = new HashSet();
expectedStates.add(ActivityState.READY);
expectedStates.add(ActivityState.EXECUTING);
throw new IllegalTaskStateException("Can't suspend task with taskUUID = ",
taskUUID, expectedStates, taskRunTime.getState());
}
final String currentUserId = EngineEnvTool.getUserId();
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Suspending: " + taskRunTime);
}
taskRunTime.suspend(currentUserId, assignTask);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Suspended: " + taskRunTime);
}
}
public void resumeTask(final TaskUUID taskUUID, final boolean taskAssign) throws TaskNotFoundException,
IllegalTaskStateException {
FacadeUtil.checkArgsNotNull(taskUUID);
final Repository repository = EngineEnvTool.getRepository();
final TaskRunTime taskRunTime = repository.getTask(taskUUID);
if (taskRunTime == null) {
throw new TaskNotFoundException(taskUUID);
}
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Checking compatible state of " + taskRunTime);
}
if (!taskRunTime.getState().equals(ActivityState.SUSPENDED)) {
final Set expectedStates = new HashSet();
expectedStates.add(ActivityState.SUSPENDED);
throw new IllegalTaskStateException("Can't suspend task with taskUUID = ",
taskUUID, expectedStates, taskRunTime.getState());
}
final String currentUserId = EngineEnvTool.getUserId();
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Resuming: " + taskRunTime);
}
taskRunTime.resume(currentUserId, taskAssign);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Resumed: " + taskRunTime);
}
}
public void assignTask(final TaskUUID taskUUID) throws TaskNotFoundException {
FacadeUtil.checkArgsNotNull(taskUUID);
final Repository repository = EngineEnvTool.getRepository();
final TaskRunTime taskRunTime = repository.getTask(taskUUID);
if (taskRunTime == null) {
throw new TaskNotFoundException(taskUUID);
}
final Performer performer = taskRunTime.getActivityDef().getPerformer();
//execute Role mapper and performerAssign
taskRunTime.resolve(performer);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Assigning: " + taskRunTime);
}
//record the assign
taskRunTime.assign(taskRunTime.getCandidates(), taskRunTime.getUserId());
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Assigned: " + taskRunTime);
}
}
public void assignTask(final TaskUUID taskUUID, final String userId) throws TaskNotFoundException {
FacadeUtil.checkArgsNotNull(taskUUID, userId);
final Repository repository = EngineEnvTool.getRepository();
final TaskRunTime taskRunTime = repository.getTask(taskUUID);
if (taskRunTime == null) {
throw new TaskNotFoundException(taskUUID);
}
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Assigning: " + taskRunTime + " to " + userId);
}
//record the assign
taskRunTime.assign(taskRunTime.getCandidates(), userId);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Assigned: " + taskRunTime + " to " + userId);
}
}
public void assignTask(final TaskUUID taskUUID, final java.util.Set candidates) throws TaskNotFoundException {
FacadeUtil.checkArgsNotNull(taskUUID, candidates);
final Repository repository = EngineEnvTool.getRepository();
final TaskRunTime taskRunTime = repository.getTask(taskUUID);
if (taskRunTime == null) {
throw new TaskNotFoundException(taskUUID);
}
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Assigning: " + taskRunTime);
}
//record the assign
taskRunTime.assign(candidates, taskRunTime.getUserId());
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Assigned: " + taskRunTime);
}
}
public void unassignTask(final TaskUUID taskUUID) throws TaskNotFoundException {
FacadeUtil.checkArgsNotNull(taskUUID);
final Repository repository = EngineEnvTool.getRepository();
final TaskRunTime taskRunTime = repository.getTask(taskUUID);
if (taskRunTime == null) {
throw new TaskNotFoundException(taskUUID);
}
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Unassigning: " + taskRunTime);
}
//record the assign
taskRunTime.assign(taskRunTime.getCandidates(), null);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Unassigned: " + taskRunTime);
}
}
private void setProcessInstanceVariable(final XpdlInstance instance, final ProcessInstanceUUID instanceUUID,
final String variableId, final Object variableValue) throws InstanceNotFoundException, VariableNotFoundException {
if (instance == null) {
throw new InstanceNotFoundException(instanceUUID);
}
final XpdlExecution rootExecution = instance.getRootExecution();
final Map globalVars = rootExecution.getScopeVariables();
if (!globalVars.containsKey(variableId)) {
throw new VariableNotFoundException(instanceUUID, variableId);
}
final ProcessDefinitionUUID processDefinitionUUID = instance.getProcessDefinitionUUID();
final PackageDefinitionUUID packageDefinitionUUID = instance.getPackageDefinitionUUID();
final ProcessFullDefinition processFullDefinition = EngineEnvTool.getAllQueriers().getProcess(processDefinitionUUID);
Misc.badStateIfNull(processFullDefinition, "can't find a process with uuid " + processDefinitionUUID);
DataTypeDefinition dataType = null;
for (final DataFieldDefinition df : processFullDefinition.getDataFields()) {
if (df.getDataFieldId().equals(variableId)) {
dataType = df.getDataType();
}
}
if (dataType == null) {
for (final FormalParameterDefinition formalParameterDefinition : processFullDefinition.getFormalParameters()) {
if (formalParameterDefinition.getId().equals(variableId)) {
dataType = formalParameterDefinition.getDataType();
}
}
}
if (dataType == null) {
final PackageFullDefinition packageFullDefinition = EngineEnvTool.getAllQueriers().getPackage(packageDefinitionUUID);
Misc.badStateIfNull(packageFullDefinition, "can't find a package with uuid " + packageDefinitionUUID);
for (final DataFieldDefinition df : processFullDefinition.getDataFields()) {
if (df.getDataFieldId().equals(variableId)) {
dataType = df.getDataType();
}
}
}
Misc.badStateIfNull(dataType, "can't find a datafield for variable " + variableId);
checkAssignmentCompatibility(variableId, variableValue, dataType, processDefinitionUUID, packageDefinitionUUID);
rootExecution.setVariable(variableId, variableValue);
}
public void setProcessInstanceVariable(final ProcessInstanceUUID instanceUUID,
final String variableId, final Object variableValue) throws InstanceNotFoundException, VariableNotFoundException {
final XpdlInstance instance = FacadeUtil.getInstance(instanceUUID);
setProcessInstanceVariable(instance, instanceUUID, variableId, variableValue);
EngineEnvTool.getRecorder().recordInstanceVariableUpdated(
variableId, variableValue, instance.getUUID(), EngineEnvTool.getUserId());
}
public void setActivityInstanceVariable(final ActivityInstanceUUID activityUUID,
final String variableId, final Object variableValue) throws ActivityNotFoundException, VariableNotFoundException {
final ActivityFullInstance activity = EngineEnvTool.getAllQueriers().getActivityInstance(activityUUID);
if (activity == null) {
throw new ActivityNotFoundException(activityUUID);
}
final ProcessInstanceUUID instanceUUID = activity.getProcessInstanceUUID();
final String activityId = activity.getActivityId();
final Repository repository = EngineEnvTool.getRepository();
XpdlExecution execution = repository.getExecutionOnActivity(
instanceUUID, activityUUID);
if (execution == null) {
throw new ActivityNotFoundException(activityUUID);
}
if (execution.hasExecution(activityId)) {
execution = (XpdlExecution) execution.getExecution(activityId);
}
final Recorder recorder = EngineEnvTool.getRecorder();
if (!execution.getScopeVariables().containsKey(variableId)) {
throw new VariableNotFoundException(instanceUUID, activityId, variableId);
}
final ProcessDefinitionUUID processDefinitionUUID = activity.getProcessDefinitionUUID();
final PackageDefinitionUUID packageDefinitionUUID = activity.getPackageDefinitionUUID();
ActivityDefinition activityDefinition;
try {
activityDefinition = new QueryDefinitionAPIImpl().getProcessActivity(
processDefinitionUUID, activityId, Querier.DEFAULT_KEY);
} catch (final ProcessNotFoundException e) {
throw new IllegalStateException(e);
}
DataTypeDefinition dataType = null;
for (final DataFieldDefinition df : activityDefinition.getDataFields()) {
if (df.getDataFieldId().equals(variableId)) {
dataType = df.getDataType();
}
}
Misc.badStateIfNull(dataType, "can't find a type for variable " + variableId);
checkAssignmentCompatibility(variableId, variableValue, dataType, processDefinitionUUID, packageDefinitionUUID);
execution.setVariable(variableId, variableValue);
// local variable updated -> update only current activity
recorder.recordActivityVariableUpdated(variableId, variableValue, execution.getCurrentActivityInstanceUUID(), EngineEnvTool.getUserId());
}
public void setVariable(final ActivityInstanceUUID activityUUID,
final String variableId, final Object variableValue) throws ActivityNotFoundException,
VariableNotFoundException {
try {
setActivityInstanceVariable(activityUUID, variableId, variableValue);
} catch (final VariableNotFoundException e) {
final ActivityFullInstance activity = EngineEnvTool.getAllQueriers().getActivityInstance(activityUUID);
if (activity == null) {
throw new ActivityNotFoundException(activityUUID);
}
try {
setProcessInstanceVariable(activity.getProcessInstanceUUID(), variableId, variableValue);
} catch (final InstanceNotFoundException e1) {
// If activity exists, the process instance must exist too.
Misc.unreachableStatement();
}
}
}
// Checks if the value can be assigned to the variable, throws an exception otherwise.
private void checkAssignmentCompatibility(final String variableId,
final Object variableValue, final DataTypeDefinition dataType,
final ProcessDefinitionUUID processUUID, final PackageDefinitionUUID packageUUID) {
if (!isAssignmentCompatible(variableValue, dataType, processUUID, packageUUID)) {
throw new BonitaWrapperException(
new TypeMismatchException("Invalid value: '" + variableValue + "' for variable '" + variableId + "'")
);
}
}
// Returns true is the object variable value can be assigned to a variable of type dataType.
private boolean isAssignmentCompatible(final Object variableValue, final DataTypeDefinition dataType,
final ProcessDefinitionUUID processUUID, final PackageDefinitionUUID packageUUID) {
switch (dataType.getType()) {
case BasicType:
final BasicTypeDefinition basicTypeDef = (BasicTypeDefinition) dataType.getValue().copy();
switch (basicTypeDef.getType()) {
case BOOLEAN:
return variableValue instanceof Boolean;
case DATETIME:
return variableValue instanceof Date;
case PERFORMER :
if (!(variableValue instanceof String)) {
return false;
}
final ProcessFullDefinition processDefinition = EngineEnvTool.getAllQueriers().getProcess(processUUID);
final PackageFullDefinition packageDefinition = EngineEnvTool.getAllQueriers().getPackage(packageUUID);
for (final ParticipantDefinition p : processDefinition.getParticipants()) {
if (p.getParticipantId().equals(variableValue)) {
return true;
}
}
for (final ParticipantDefinition p : packageDefinition.getParticipants()) {
if (p.getParticipantId().equals(variableValue)) {
return true;
}
}
return false;
case FLOAT :
// xpdl spec says a float can b a float or a double, in bonita it is a Double
return variableValue instanceof Double;
case INTEGER :
return variableValue instanceof Long;
case STRING :
return variableValue instanceof String;
default:
return false;
}
case EnumerationType:
return variableValue instanceof Enumeration;
default:
throw new UnsupportedOperationException();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy