blob: 9e6ab7e2536e7c2124667fa79549abfb6d473e9d [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010 BSI Business Systems Integration AG.
* 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:
* BSI Business Systems Integration AG - initial API and implementation
******************************************************************************/
package org.eclipse.scout.rt.server;
import java.lang.reflect.UndeclaredThrowableException;
import java.security.AccessController;
import java.security.Principal;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Locale;
import java.util.Map;
import javax.security.auth.Subject;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.scout.commons.LocaleThreadLocal;
import org.eclipse.scout.commons.StringUtility;
import org.eclipse.scout.commons.exception.ProcessingException;
import org.eclipse.scout.commons.exception.ProcessingStatus;
import org.eclipse.scout.commons.job.JobEx;
import org.eclipse.scout.commons.logger.IScoutLogger;
import org.eclipse.scout.commons.logger.ScoutLogManager;
import org.eclipse.scout.commons.serialization.SerializationUtility;
import org.eclipse.scout.rt.server.internal.Activator;
import org.eclipse.scout.rt.server.transaction.BasicTransaction;
import org.eclipse.scout.rt.server.transaction.ITransaction;
import org.eclipse.scout.rt.server.transaction.internal.ActiveTransactionRegistry;
import org.eclipse.scout.rt.shared.ScoutTexts;
import org.eclipse.scout.rt.shared.TextsThreadLocal;
/**
* Perform a transaction on a {@link IServerSession}<br>
*/
public abstract class ServerJob extends JobEx implements IServerSessionProvider {
private static final IScoutLogger LOG = ScoutLogManager.getLogger(ServerJob.class);
//use classloader from SerializationUtility in Server Job
private static final String customClassloaderProperty = "org.eclipse.scout.rt.server.customServerJobClassloader";
private final IServerSession m_serverSession;
private Subject m_subject;
private long m_transactionSequence;
private final boolean m_useCostomClassLoader;
/**
* Perform a transaction on a {@link IServerSession} within a security {@link Subject} (optional)<br>
*
* @param serverSession
* must not be null
*/
public ServerJob(String name, IServerSession serverSession) {
this(name, serverSession, null);
}
/**
* Perform a transaction on a {@link IServerSession} within a security {@link Subject} (optional)<br>
*
* @param serverSession
* must not be null
* @param subject
* (optional) the job is run inside a {@link Subject#doAs(Subject, java.security.PrivilegedAction)} section
*/
public ServerJob(String name, IServerSession serverSession, Subject subject) {
super(name);
if (serverSession == null) {
throw new IllegalArgumentException("serverSession is null");
}
m_serverSession = serverSession;
m_subject = subject;
m_useCostomClassLoader = isUseCustomClassloader();
}
private boolean isUseCustomClassloader() {
try {
return StringUtility.parseBoolean(Activator.getDefault().getBundle().getBundleContext().getProperty(customClassloaderProperty));
}
catch (Exception e) {
return false;
}
}
/**
* {@link ServerJob}s belong to the family of type {@link ServerJob}.class
*/
@Override
public boolean belongsTo(Object family) {
if (family == ServerJob.class) {
return true;
}
return false;
}
@Override
public IServerSession getServerSession() {
return m_serverSession;
}
public Subject getSubject() {
return m_subject;
}
/**
* The subject can only be modified as long as the job is not scheduled
*/
public void setSubject(Subject subject) {
if (getState() != NONE) {
throw new IllegalStateException("This job is already scheduled/running");
}
m_subject = subject;
}
/**
* see {@link ITransaction#getTransactionSequence()}
*/
public long getTransactionSequence() {
return m_transactionSequence;
}
/**
* see {@link ITransaction#getTransactionSequence()}
*/
public void setTransactionSequence(long seq) {
if (getState() != NONE) {
throw new IllegalStateException("This job is already scheduled/running");
}
m_transactionSequence = seq;
}
@Override
public boolean shouldSchedule() {
if (getServerSession() != null && getServerSession().isSingleThreadSession()) {
runNow(new NullProgressMonitor());
return false;
}
else {
return super.shouldSchedule();
}
}
/**
* <p>
* All subsequent calls within {@link ServerJob#runTransaction(IProgressMonitor))} have the {@link IServerSession} set
* in their thread context. After execution completes, {@link IServerSession} is cleared from the thread context.
* </p>
* <p>
* By calling this method, a new transaction on {@link IServerSession} is created and automatically comitted after
* successful completion.
* </p>
*
* @param monitor
*/
@Override
public final IStatus runNow(IProgressMonitor monitor) {
return super.runNow(monitor);
}
@Override
protected final IStatus run(final IProgressMonitor monitor) {
try {
if (m_subject != null) {
try {
return Subject.doAs(
m_subject,
new PrivilegedExceptionAction<IStatus>() {
@Override
public IStatus run() throws Exception {
return runTransactionWrapper(monitor);
}
}
);
}
catch (PrivilegedActionException e) {
Throwable t = e.getCause();
if (t instanceof ProcessingException) {
return ((ProcessingException) t).getStatus();
}
else {
return new ProcessingStatus(t.getMessage(), t, 0, ProcessingStatus.ERROR);
}
}
}
else {
return runTransactionWrapper(monitor);
}
}
catch (Throwable t) {
if (t instanceof ProcessingException) {
return ((ProcessingException) t).getStatus();
}
else {
return new ProcessingStatus(t.getMessage(), t, 0, ProcessingStatus.ERROR);
}
}
}
private IStatus runTransactionWrapper(IProgressMonitor monitor) throws Exception {
ITransaction transaction = createNewTransaction();
Map<Class, Object> backup = ThreadContext.backup();
Locale oldLocale = LocaleThreadLocal.get();
ScoutTexts oldTexts = TextsThreadLocal.get();
ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader();
try {
ThreadContext.putServerSession(m_serverSession);
ThreadContext.putTransaction(transaction);
LocaleThreadLocal.set(m_serverSession.getLocale());
TextsThreadLocal.set(m_serverSession.getTexts());
ActiveTransactionRegistry.register(transaction);
if (m_useCostomClassLoader) {
Thread.currentThread().setContextClassLoader(SerializationUtility.getClassLoader());
}
//
IStatus status = runTransaction(monitor);
if (status == null) {
status = Status.OK_STATUS;
}
return status;
}
catch (Throwable t) {
if (t instanceof UndeclaredThrowableException) {
Throwable test = ((UndeclaredThrowableException) t).getUndeclaredThrowable();
if (test != null) {
t = test;
}
}
if (t.getCause() instanceof ProcessingException) {
t = t.getCause();
}
transaction.addFailure(t);
String contextMsg = "Identity=" + ServerJob.getIdentity() + ", Job=" + getName();
ProcessingException pe;
if (t instanceof ProcessingException) {
pe = (ProcessingException) t;
pe.addContextMessage(contextMsg);
}
else {
pe = new ProcessingException(contextMsg, t);
}
throw pe;
}
finally {
ActiveTransactionRegistry.unregister(transaction);
if (transaction.hasFailures()) {
// xa rollback
try {
transaction.rollback();
}
catch (Throwable t) {
LOG.error("Transaction rollback failed with exception.", t);
}
}
else {
// xa commit
boolean needRollback = false;
try {
if (transaction.commitPhase1()) {
transaction.commitPhase2();
}
else {
needRollback = true;
}
}
catch (Throwable t) {
needRollback = true;
LOG.error("Transaction commit exception.", t);
}
if (needRollback) {
// rollback
try {
transaction.rollback();
}
catch (Throwable t) {
LOG.error("Transaction rollback failed with exception.", t);
}
}
}
// xa release
try {
transaction.release();
}
catch (Throwable t) {
LOG.warn(null, t);
}
// restore thread context
try {
ThreadContext.restore(backup);
}
catch (Throwable t) {
LOG.warn(null, t);
}
LocaleThreadLocal.set(oldLocale);
TextsThreadLocal.set(oldTexts);
Thread.currentThread().setContextClassLoader(oldContextClassLoader);
}
}
protected abstract IStatus runTransaction(IProgressMonitor monitor) throws Exception;
/**
* @return {@link ThreadContext#getServerSession()}
*/
public static final IServerSession getCurrentSession() {
return getCurrentSession(IServerSession.class);
}
/**
* @return {@link ThreadContext#getServerSession()} and check if it matches the required type
*/
@SuppressWarnings("unchecked")
public static final <T extends IServerSession> T getCurrentSession(Class<T> type) {
IServerSession s = ThreadContext.getServerSession();
if (s != null && type.isAssignableFrom(s.getClass())) {
return (T) s;
}
return null;
}
/**
* Convenience to obtain name of current subjects first principal
*/
public static String getIdentity() {
Subject subject = Subject.getSubject(AccessController.getContext());
if (subject != null) {
for (Principal p : subject.getPrincipals()) {
return p.getName();
}
}
return "anonymous";
}
protected ITransaction createNewTransaction() {
return new BasicTransaction(getTransactionSequence());
}
}