blob: 6d7428544f7b0b356660112c35594d3cf2f77bf7 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008-2011 Chair for Applied Software Engineering,
* Technische Universitaet Muenchen.
* 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:
* Maximilian Koegel, Edgar Mueller - initial API and implementation
* Johannes Faltermeier - EMFStore specific URI migration
******************************************************************************/
package org.eclipse.emf.emfstore.internal.client.model;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import org.apache.commons.lang.StringUtils;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.emf.common.notify.AdapterFactory;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
import org.eclipse.emf.edit.provider.ReflectiveItemProviderAdapterFactory;
import org.eclipse.emf.emfstore.client.ESLocalProject;
import org.eclipse.emf.emfstore.client.ESWorkspaceProvider;
import org.eclipse.emf.emfstore.client.observer.ESCheckoutObserver;
import org.eclipse.emf.emfstore.client.observer.ESCommitObserver;
import org.eclipse.emf.emfstore.client.observer.ESShareObserver;
import org.eclipse.emf.emfstore.client.observer.ESUpdateObserver;
import org.eclipse.emf.emfstore.client.observer.ESWorkspaceInitObserver;
import org.eclipse.emf.emfstore.client.provider.ESEditingDomainProvider;
import org.eclipse.emf.emfstore.client.sessionprovider.ESAbstractSessionProvider;
import org.eclipse.emf.emfstore.client.util.ESClientURIUtil;
import org.eclipse.emf.emfstore.client.util.RunESCommand;
import org.eclipse.emf.emfstore.common.ESResourceSetProvider;
import org.eclipse.emf.emfstore.common.extensionpoint.ESExtensionElement;
import org.eclipse.emf.emfstore.common.extensionpoint.ESExtensionPoint;
import org.eclipse.emf.emfstore.common.extensionpoint.ESExtensionPointException;
import org.eclipse.emf.emfstore.common.extensionpoint.ESPriorityComparator;
import org.eclipse.emf.emfstore.internal.client.model.changeTracking.commands.EMFStoreBasicCommandStack;
import org.eclipse.emf.emfstore.internal.client.model.connectionmanager.AdminConnectionManager;
import org.eclipse.emf.emfstore.internal.client.model.connectionmanager.ConnectionManager;
import org.eclipse.emf.emfstore.internal.client.model.connectionmanager.KeyStoreManager;
import org.eclipse.emf.emfstore.internal.client.model.connectionmanager.SessionManager;
import org.eclipse.emf.emfstore.internal.client.model.connectionmanager.xmlrpc.XmlRpcAdminConnectionManager;
import org.eclipse.emf.emfstore.internal.client.model.connectionmanager.xmlrpc.XmlRpcConnectionManager;
import org.eclipse.emf.emfstore.internal.client.model.impl.WorkspaceImpl;
import org.eclipse.emf.emfstore.internal.client.model.impl.api.ESWorkspaceImpl;
import org.eclipse.emf.emfstore.internal.client.model.util.EMFStoreCommand;
import org.eclipse.emf.emfstore.internal.client.model.util.WorkspaceUtil;
import org.eclipse.emf.emfstore.internal.client.provider.ClientXMIResourceSetProvider;
import org.eclipse.emf.emfstore.internal.client.startup.ClientHrefMigrator;
import org.eclipse.emf.emfstore.internal.common.ESDisposable;
import org.eclipse.emf.emfstore.internal.common.ESRunnableWrapperProvider;
import org.eclipse.emf.emfstore.internal.common.model.Project;
import org.eclipse.emf.emfstore.internal.common.model.util.ModelUtil;
import org.eclipse.emf.emfstore.internal.common.observer.ObserverBus;
import org.eclipse.emf.emfstore.server.model.ESChangePackage;
import org.eclipse.emf.emfstore.server.model.versionspec.ESPrimaryVersionSpec;
/**
* Controller for workspaces. Different threads can have a different instances associated with them.
* This is useful in the situation common where the client is a webserver,
* and different user sessions have different threads associated with them.
*
* @author mkoegel
* @author emueller
* @author jfaltermeier
*/
public final class ESWorkspaceProviderImpl implements ESWorkspaceProvider, ESCommitObserver, ESUpdateObserver,
ESShareObserver, ESCheckoutObserver, ESDisposable {
private static final String WORKSPACE_INIT_EXT_POINT_ID = "org.eclipse.emf.emfstore.client.workspaceInitObserver"; //$NON-NLS-1$
/**
* This thread local variable stores the ESWorkspaceProviderImpl associated with this thread.
* different threads may have different instances associated with them. This allows the EMFStore
* client code to provide a separate workspace provider for different threads.This is useful in the
* situation common where the client is a webserver, and different user sessions have different
* threads associated with them. When new threads are started this this threadlocal variable
* is inherited by the newly created thread.
*/
private static final ThreadLocal<ESWorkspaceProviderImpl> WS_THREAD_LOCAL = new InheritableThreadLocal<ESWorkspaceProviderImpl>();
private static final String DEFAULT_WORKSPACE_ID = "default"; //$NON-NLS-1$
private static ESWorkspaceProviderImpl defaultInstance;
private String id;
private AdminConnectionManager adminConnectionManager;
private ConnectionManager connectionManager;
private EditingDomain editingDomain;
private ObserverBus observerBus;
private ResourceSet resourceSet;
private SessionManager sessionManager;
private Workspace currentWorkspace;
/**
* Default constructor.
*/
public ESWorkspaceProviderImpl() {
id = StringUtils.EMPTY;
}
/**
* Constructor that creates an instance with a specific ID.
*
* @param id
* the workspace identifier for the instance
*
*/
public ESWorkspaceProviderImpl(String id) {
this.id = id;
}
/**
* This method is used by {@link org.eclipse.emf.emfstore.internal.client.common.ScopedWorkspaceThreadPoolExecutor
* ScopedWorkspaceThreadPoolExecutor} which acts as a customized threadpool.
* When behavior is run in a
* {@link org.eclipse.emf.emfstore.internal.client.common.ScopedWorkspaceThreadPoolExecutor
* ScopedWorkspaceThreadPoolExecutor}, the pool
* first ensures that the thread local ESWorkspaceProviderImpl is taken from the thread that called for
* behavior to be run in the threadpool, and then set as the
* threadlocal instance for the thread running in the behavior in the threadpool.
*
*
* @param runnable
* the {@link Runnable} to be executed in the context of the current workspace
*
* @return the updated {@link Runnable} that accordingly sets the workspace
*/
public static Runnable initRunnable(final Runnable runnable) {
final ESWorkspaceProviderImpl ws = WS_THREAD_LOCAL.get();
return ESRunnableWrapperProvider.getInstance().embedInContext(new Runnable() {
public void run() {
WS_THREAD_LOCAL.set(ws);
runnable.run();
}
});
}
/**
* @return the name
*/
public String getName() {
return id;
}
/**
* @param name the name to set
*/
public void setName(String name) {
id = name;
}
/**
* This method retrieves the instance of ESWorkspaceProviderImpl associated with this thread.
* If there is not yet an associated instance, and no default instance then an instance
* is created and associated with the calling thread.
*
* @return the WorkspaceProvider for this thread
*/
public static ESWorkspaceProviderImpl getInstance() {
if (WS_THREAD_LOCAL.get() == null) {
if (defaultInstance == null) {
try {
defaultInstance = new ESWorkspaceProviderImpl(DEFAULT_WORKSPACE_ID);
defaultInstance.initialize();
// BEGIN SUPRESS CATCH EXCEPTION
} catch (final RuntimeException e) {
// END SURPRESS CATCH EXCEPTION
ModelUtil.logException(Messages.ESWorkspaceProviderImpl_WorkspaceInit_Failed, e);
throw e;
}
// notify post workspace observers
defaultInstance.notifyPostWorkspaceInitiators();
}
return defaultInstance;
}
return WS_THREAD_LOCAL.get();
}
/**
* This method retrieves the workspace provider instance associated with the given ID.
* If there is not yet an associated workspace with the given ID, then an instance
* is created and associated with the calling thread.
*
* @param workspaceProviderId
* the workspace identifier, usually a session id
* @return the WorkspaceProvider associated this workspaceIdentifier
*/
public static ESWorkspaceProviderImpl getInstance(String workspaceProviderId) {
if (WorkspaceLocator.hasId(workspaceProviderId)) {
return WorkspaceLocator.getWorkspaceById(workspaceProviderId);
}
final ESWorkspaceProviderImpl ws = WorkspaceLocator.createWorkspaceProviderFor(workspaceProviderId);
WS_THREAD_LOCAL.set(ws);
return ws;
}
/**
* Initialize the Workspace Manager.
*/
public static synchronized void init() {
getInstance();
}
/**
* Retrieve the editing domain.
*
* @return the workspace editing domain
*/
public EditingDomain getEditingDomain() {
if (editingDomain == null) {
ESWorkspaceProviderImpl.getInstance();
}
return editingDomain;
}
/**
* Sets the EditingDomain.
*
* @param editingDomain
* new domain.
*/
public void setEditingDomain(EditingDomain editingDomain) {
this.editingDomain = editingDomain;
}
/**
*
* {@inheritDoc}
*
* @see org.eclipse.emf.emfstore.internal.common.ESDisposable#dispose()
*/
public void dispose() {
if (currentWorkspace != null) {
RunESCommand.run(new Callable<Void>() {
public Void call() throws Exception {
((WorkspaceImpl) currentWorkspace).dispose();
return null;
}
});
currentWorkspace = null;
}
}
/**
* Whether the current workspace is disposed.
*
* @return {@code true} if the current workspace is disposed, {@code false} otherwise
*/
public boolean isDisposed() {
return currentWorkspace == null;
}
/**
* (Re-)Initializes the workspace. Loads workspace from persistent storage
* if present. There is always one current Workspace.
*/
public void load() {
final ESExtensionPoint extensionPoint = new ESExtensionPoint(
"org.eclipse.emf.emfstore.client.resourceSetProvider", //$NON-NLS-1$
true, new ESPriorityComparator("priority", true)); //$NON-NLS-1$
final ESResourceSetProvider resourceSetProvider = extensionPoint.getElementWithHighestPriority().getClass(
"class", //$NON-NLS-1$
ESResourceSetProvider.class);
resourceSet = resourceSetProvider.getResourceSet();
// register an editing domain on the resource
setEditingDomain(createEditingDomain(resourceSet));
final URI workspaceURI = ESClientURIUtil.createWorkspaceURI();
final Workspace workspace;
final Resource resource;
if (!resourceSet.getURIConverter().exists(workspaceURI, null)) {
workspace = createNewWorkspace(resourceSet, workspaceURI);
} else {
// hrefs are persisted differently in 1.1+ in comparison to 1.0
// migrate, if needed, before loading
if (resourceSetProvider instanceof ClientXMIResourceSetProvider) {
if (!new ClientHrefMigrator().migrate()) {
throw new RuntimeException(Messages.ESWorkspaceProviderImpl_Migration_Failed);
}
}
// file exists, load it,
// check if a migration is needed
migrateModelIfNeeded(resourceSet);
resource = resourceSet.createResource(workspaceURI);
try {
resource.load(ModelUtil.getResourceLoadOptions());
} catch (final IOException e) {
WorkspaceUtil.logException(Messages.ESWorkspaceProviderImpl_Workspace_Loading_Failed, e);
}
final EList<EObject> directContents = resource.getContents();
workspace = (Workspace) directContents.get(0);
}
workspace.setResourceSet(resourceSet);
RunESCommand.run(new Callable<Void>() {
public Void call() throws Exception {
workspace.init();
return null;
}
});
currentWorkspace = workspace;
getObserverBus().register(this);
}
/**
* Flushes the command stack.
*/
public void flushCommandStack() {
getEditingDomain().getCommandStack().flush();
}
// public void migrate(String absoluteFilename) {
// FIXME JF
// final URI projectURI = URI.createFileURI(absoluteFilename);
//
// final List<URI> modelURIs = new ArrayList<URI>();
// modelURIs.add(projectURI);
//
// final ModelVersion workspaceModelVersion = getWorkspaceModelVersion();
// if (!EMFStoreMigratorUtil.isMigratorAvailable()) {
// ModelUtil.logWarning("No Migrator available to migrate imported file");
// return;
// }
//
// try {
// EMFStoreMigratorUtil.getEMFStoreMigrator().migrate(modelURIs, workspaceModelVersion.getReleaseNumber() - 1,
// new NullProgressMonitor());
// } catch (final EMFStoreMigrationException e) {
// WorkspaceUtil.logWarning("The migration of the project in the file " + absoluteFilename + " failed!", e);
// }
// }
/**
* Get the admin connection manager. Return the admin connection manager for
* this workspace.
*
* @return the connectionManager
*/
public AdminConnectionManager getAdminConnectionManager() {
return adminConnectionManager;
}
/**
* Set the admin connection manager.
*
* @param adminConnectionManager
* the new {@link AdminConnectionManager} to be set
*/
public void setAdminConnectionManager(AdminConnectionManager adminConnectionManager) {
this.adminConnectionManager = adminConnectionManager;
}
/**
* Retrieve the project space for a model element.
*
* @param modelElement
* the model element
* @return the project space
*/
public static ProjectSpace getProjectSpace(EObject modelElement) {
if (modelElement == null) {
throw new IllegalArgumentException(Messages.ESWorkspaceProviderImpl_ModelElement_Is_Null);
} else if (modelElement instanceof ProjectSpace) {
return (ProjectSpace) modelElement;
}
final Project project = ModelUtil.getProject(modelElement);
if (project == null) {
throw new IllegalArgumentException(MessageFormat.format(
Messages.ESWorkspaceProviderImpl_ModelElement_Has_No_Project, modelElement));
}
return getProjectSpace(project);
}
/**
* Retrieve the project space for a project.
*
* @param project
* the project
* @return the project space
*/
public static ProjectSpace getProjectSpace(Project project) {
if (project == null) {
throw new IllegalArgumentException(Messages.ESWorkspaceProviderImpl_Project_Is_Null);
}
// check if my container is a project space
if (ModelPackage.eINSTANCE.getProjectSpace().isInstance(project.eContainer())) {
return (ProjectSpace) project.eContainer();
}
throw new IllegalStateException(Messages.ESWorkspaceProviderImpl_Project_Not_Contained_By_ProjectSpace);
}
/**
* Returns the {@link ObserverBus}.
*
* @return observer bus
*/
public static ObserverBus getObserverBus() {
return getInstance().observerBus;
}
/**
* Returns the {@link SessionManager}.
*
* @return session manager
*/
public SessionManager getSessionManager() {
return sessionManager;
}
/**
* Get the current workspace. There is always one current workspace.
*
* @return the workspace
*/
public ESWorkspaceImpl getWorkspace() {
return RunESCommand.runWithResult(new Callable<ESWorkspaceImpl>() {
public ESWorkspaceImpl call() throws Exception {
return getInternalWorkspace().toAPI();
}
});
}
/**
* Returns the internal workspace.
*
* @return the workspace
*/
public Workspace getInternalWorkspace() {
if (currentWorkspace == null) {
load();
}
return currentWorkspace;
}
/**
* Get the connection manager. Return the connection manager for this
* workspace.
*
* @return the connectionManager
*/
public ConnectionManager getConnectionManager() {
return connectionManager;
}
/**
* Set the connectionmanager.
*
* @param manager
* connection manager.
*/
public void setConnectionManager(ConnectionManager manager) {
connectionManager = manager;
}
/**
*
* {@inheritDoc}
*
* @see ESWorkspaceProvider#setSessionProvider(ESAbstractSessionProvider)
*/
public void setSessionProvider(ESAbstractSessionProvider sessionProvider) {
getSessionManager().setSessionProvider(sessionProvider);
}
/**
* Initializes the workspace.
*/
void initialize() {
observerBus = new ObserverBus();
connectionManager = initConnectionManager();
adminConnectionManager = initAdminConnectionManager();
sessionManager = new SessionManager();
load();
}
/**
* Notifies all {@link ESWorkspaceInitObserver} that the workspace initialization is complete.
*/
void notifyPostWorkspaceInitiators() {
final List<ESExtensionElement> extensionElements = new ESExtensionPoint(WORKSPACE_INIT_EXT_POINT_ID, true)
.getExtensionElements();
for (final ESExtensionElement element : extensionElements) {
try {
element
.getClass("class", ESWorkspaceInitObserver.class) //$NON-NLS-1$
.workspaceInitComplete(currentWorkspace.toAPI());
} catch (final ESExtensionPointException e) {
WorkspaceUtil.logException(e.getMessage(), e);
}
}
}
/**
* Initialize the connection manager of the workspace. The connection
* manager connects the workspace with EMFStore.
*
* @return the connection manager
*/
private ConnectionManager initConnectionManager() {
KeyStoreManager.getInstance().setupKeys();
return new XmlRpcConnectionManager();
}
/**
* Initialize the connection manager of the workspace. The connection
* manager connects the workspace with the emf store.
*
* @return the admin connection manager
* @generated NOT
*/
private AdminConnectionManager initAdminConnectionManager() {
return new XmlRpcAdminConnectionManager();
}
private EditingDomain createEditingDomain(ResourceSet resourceSet) {
final ESEditingDomainProvider domainProvider = getDomainProvider();
if (domainProvider != null) {
return domainProvider.getEditingDomain(resourceSet);
}
AdapterFactory adapterFactory = new ComposedAdapterFactory(
ComposedAdapterFactory.Descriptor.Registry.INSTANCE);
adapterFactory = new ComposedAdapterFactory(new AdapterFactory[] { adapterFactory,
new ReflectiveItemProviderAdapterFactory() });
final AdapterFactoryEditingDomain domain = new AdapterFactoryEditingDomain(adapterFactory,
new EMFStoreBasicCommandStack(), resourceSet);
resourceSet.eAdapters().add(new AdapterFactoryEditingDomain.EditingDomainProvider(domain));
return domain;
}
private ESEditingDomainProvider getDomainProvider() {
// TODO EXPT PRIO
return new ESExtensionPoint("org.eclipse.emf.emfstore.client.editingDomainProvider") //$NON-NLS-1$
.getClass("class", //$NON-NLS-1$
ESEditingDomainProvider.class);
}
private Workspace createNewWorkspace(ResourceSet resourceSet, URI fileURI) {
final Workspace workspace;
final Resource resource;
// no workspace content found, create a workspace
resource = resourceSet.createResource(fileURI);
workspace = ModelFactory.eINSTANCE.createWorkspace();
workspace.getServerInfos().addAll(Configuration.getClientBehavior().getDefaultServerInfos());
final EList<Usersession> usersessions = workspace.getUsersessions();
for (final ServerInfo serverInfo : workspace.getServerInfos()) {
final Usersession lastUsersession = serverInfo.getLastUsersession();
if (lastUsersession != null) {
usersessions.add(lastUsersession);
}
}
new EMFStoreCommand() {
@Override
protected void doRun() {
resource.getContents().add(workspace);
}
}.run(true);
try {
resource.save(ModelUtil.getResourceSaveOptions());
} catch (final IOException e) {
WorkspaceUtil.logException(
Messages.ESWorkspaceProviderImpl_Create_Workspace_Failed
+ Configuration.getFileInfo().getWorkspaceDirectory(),
e);
}
return workspace;
}
private void migrateModelIfNeeded(ResourceSet resourceSet) {
// FIXME JF: currently we will only support server side migration
// EMFStoreMigrator migrator = null;
// try {
// migrator = EMFStoreMigratorUtil.getEMFStoreMigrator();
// } catch (final EMFStoreMigrationException e2) {
// WorkspaceUtil.logWarning(e2.getMessage(), null);
// return;
// }
//
// for (final List<URI> curModels : getPhysicalURIsForMigration()) {
// // TODO logging?
// if (!migrator.canHandle(curModels)) {
// return;
// }
//
// if (!migrator.needsMigration(curModels)) {
// return;
// }
//
// try {
// migrator.migrate(curModels, new NullProgressMonitor());
// } catch (final EMFStoreMigrationException e) {
// WorkspaceUtil.logException(MessageFormat.format(
// Messages.ESWorkspaceProviderImpl_Migration_Of_Project_Failed, curModels.get(0)), e);
// }
// }
}
private List<List<URI>> getPhysicalURIsForMigration() {
final ESExtensionPoint extensionPoint = new ESExtensionPoint(
"org.eclipse.emf.emfstore.client.resourceSetProvider", //$NON-NLS-1$
true, new ESPriorityComparator("priority", true)); //$NON-NLS-1$
final ESResourceSetProvider resourceSetProvider = extensionPoint.getElementWithHighestPriority().getClass(
"class", //$NON-NLS-1$
ESResourceSetProvider.class);
final ResourceSet migrationResourceSet = resourceSetProvider.getResourceSet();
final Resource resource = migrationResourceSet.createResource(ESClientURIUtil.createWorkspaceURI());
try {
resource.load(ModelUtil.getResourceLoadOptions());
} catch (final IOException e) {
WorkspaceUtil.logException(Messages.ESWorkspaceProviderImpl_Workspace_Loading_Failed, e);
}
final List<List<URI>> physicalURIs = new ArrayList<List<URI>>();
final EList<EObject> directContents = resource.getContents();
final Workspace workspace = (Workspace) directContents.get(0);
for (final ProjectSpace ps : workspace.getProjectSpaces()) {
final List<URI> uris = new ArrayList<URI>();
final URI projectURI = migrationResourceSet.getURIConverter().normalize(
ps.getProject().eResource().getURI());
final URI operationsURI = migrationResourceSet.getURIConverter()
.normalize(ps.getLocalChangePackage().eResource().getURI());
uris.add(projectURI);
uris.add(operationsURI);
physicalURIs.add(uris);
}
return physicalURIs;
}
/**
*
* {@inheritDoc}
*
* @see org.eclipse.emf.emfstore.client.observer.ESCheckoutObserver#checkoutDone(org.eclipse.emf.emfstore.client.ESLocalProject)
*/
public void checkoutDone(ESLocalProject project) {
flushCommandStack();
}
/**
*
* {@inheritDoc}
*
* @see org.eclipse.emf.emfstore.client.observer.ESShareObserver#shareDone(org.eclipse.emf.emfstore.client.ESLocalProject)
*/
public void shareDone(ESLocalProject localProject) {
flushCommandStack();
}
/**
*
* {@inheritDoc}
*
* @see org.eclipse.emf.emfstore.client.observer.ESUpdateObserver#inspectChanges(org.eclipse.emf.emfstore.client.ESLocalProject,
* java.util.List, org.eclipse.core.runtime.IProgressMonitor)
*/
public boolean inspectChanges(ESLocalProject project, List<ESChangePackage> changePackages,
IProgressMonitor monitor) {
return true;
}
/**
*
* {@inheritDoc}
*
* @see org.eclipse.emf.emfstore.client.observer.ESUpdateObserver#updateCompleted(org.eclipse.emf.emfstore.client.ESLocalProject,
* org.eclipse.core.runtime.IProgressMonitor)
*/
public void updateCompleted(ESLocalProject project, IProgressMonitor monitor) {
flushCommandStack();
}
/**
*
* {@inheritDoc}
*
* @see org.eclipse.emf.emfstore.client.observer.ESCommitObserver#inspectChanges(org.eclipse.emf.emfstore.client.ESLocalProject,
* org.eclipse.emf.emfstore.server.model.ESChangePackage, org.eclipse.core.runtime.IProgressMonitor)
*/
public boolean inspectChanges(ESLocalProject project, ESChangePackage changePackage, IProgressMonitor monitor) {
return true;
}
/**
*
* {@inheritDoc}
*
* @see org.eclipse.emf.emfstore.client.observer.ESCommitObserver#commitCompleted(org.eclipse.emf.emfstore.client.ESLocalProject,
* org.eclipse.emf.emfstore.server.model.versionspec.ESPrimaryVersionSpec,
* org.eclipse.core.runtime.IProgressMonitor)
*/
public void commitCompleted(ESLocalProject project, ESPrimaryVersionSpec newRevision, IProgressMonitor monitor) {
flushCommandStack();
}
}