/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-2.0
*
* 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 li.strolch.service.api;
import java.text.MessageFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import li.strolch.agent.api.ComponentContainer;
import li.strolch.agent.api.StrolchComponent;
import li.strolch.agent.api.StrolchRealm;
import li.strolch.exception.StrolchException;
import li.strolch.persistence.api.StrolchTransaction;
import li.strolch.privilege.base.PrivilegeException;
import li.strolch.privilege.handler.SystemUserAction;
import li.strolch.privilege.model.Certificate;
import li.strolch.privilege.model.PrivilegeContext;
import li.strolch.runtime.StrolchConstants;
import li.strolch.runtime.configuration.RuntimeConfiguration;
import li.strolch.runtime.privilege.PrivilegeHandler;
import li.strolch.utils.dbc.DBC;
/**
* @author Robert von Burg <eitch@eitchnet.ch>
*/
public abstract class AbstractService<T extends ServiceArgument, U extends ServiceResult> implements Service<T, U> {
protected static final Logger logger = LoggerFactory.getLogger(AbstractService.class);
private static final long serialVersionUID = 1L;
private ComponentContainer container;
private PrivilegeContext privilegeContext;
/**
* Called by the {@link ServiceHandler} to set the {@link PrivilegeContext} before this service is performed
*
* @param privilegeContext
* the privilegeContext to set
*/
public final void setPrivilegeContext(PrivilegeContext privilegeContext) {
DBC.PRE.assertNull("PrivilegeContext is already set!", this.privilegeContext); //$NON-NLS-1$
this.privilegeContext = privilegeContext;
}
/**
* Return the {@link PrivilegeContext} to perform further privilege authorization validation
*
* @return the privilegeContext
*/
public final PrivilegeContext getPrivilegeContext() {
return this.privilegeContext;
}
/**
* Returns the {@link Certificate} of the user who is performing this service
*
* @return the certificate
*/
protected final Certificate getCertificate() {
return this.privilegeContext.getCertificate();
}
/**
* Called by the {@link ServiceHandler} to set a reference to the {@link ComponentContainer} to be used during
* service execution
*
* @param container
* the container to set
*/
public final void setContainer(ComponentContainer container) {
this.container = container;
}
/**
* Returns the reference to the {@link ComponentContainer}
*
* @return the container
*/
protected final ComponentContainer getContainer() {
return this.container;
}
/**
* Returns the reference to the {@link PrivilegeHandler}
*
* @return the privilege handler
*/
public PrivilegeHandler getPrivilegeHandler() throws IllegalArgumentException {
return this.container.getPrivilegeHandler();
}
/**
* Returns the reference to the {@link StrolchComponent} with the given name, if it exists. If it does not exist, an
* {@link IllegalArgumentException} is thrown
*
* @param clazz
*
* @return the component with the given name
*
* @throws IllegalArgumentException
* if the component does not exist
*/
protected final <V> V getComponent(Class<V> clazz) {
return this.container.getComponent(clazz);
}
/**
* Returns the Strolch {@link RuntimeConfiguration}
*
* @return the Strolch {@link RuntimeConfiguration}
*/
protected final RuntimeConfiguration getRuntimeConfiguration() {
return this.container.getAgent().getStrolchConfiguration().getRuntimeConfiguration();
}
/**
* Returns the {@link StrolchRealm} with the given name. If the realm does not exist, then a
* {@link StrolchException} is thrown
*
* @param realm
* the name of the {@link StrolchRealm} to return
* @return the {@link StrolchRealm} with the given name
*
* @throws StrolchException
* if the {@link StrolchRealm} does not exist with the given name
*/
protected final StrolchRealm getRealm(String realm) throws StrolchException {
return this.container.getRealm(realm);
}
/**
* Opens a {@link StrolchTransaction} for the given realm, the action for the TX is this implementation's class
* name. This transaction should be used in a try-with-resource clause so it is properly closed
*
* @param realm
* the name of the realm to return
*
* @return the realm with the given name
*
* @throws StrolchException
* if the {@link StrolchRealm} does not exist with the given name
*/
protected StrolchTransaction openTx(String realm) throws StrolchException {
return this.container.getRealm(realm).openTx(getCertificate(), getClass());
}
/**
* Opens a {@link StrolchTransaction} for the given realm. This transaction should be used in a try-with-resource
* clause so it is properly closed
*
* @param realm
* the name of the realm to return
* @param action
* the action to use for the opened TX
*
* @return the realm with the given name
*
* @throws StrolchException
* if the {@link StrolchRealm} does not exist with the given name
*/
protected StrolchTransaction openTx(String realm, String action) throws StrolchException {
return this.container.getRealm(realm).openTx(getCertificate(), action);
}
/**
* Opens a {@link StrolchTransaction} where the realm retrieved using
* {@link ComponentContainer#getRealm(Certificate)}, the action for the TX is this implementation's class name. This
* transaction should be used in a try-with-resource clause so it is properly closed
*
* @return the realm with the given name
*
* @throws StrolchException
* if the {@link StrolchRealm} does not exist with the given name
*/
protected StrolchTransaction openUserTx() throws StrolchException {
return this.container.getRealm(getCertificate()).openTx(getCertificate(), getClass());
}
/**
* Opens a {@link StrolchTransaction} where the realm retrieved using
* {@link ComponentContainer#getRealm(Certificate)}. This transaction should be used in a try-with-resource clause
* so it is properly closed
*
* @param realm
* the name of the realm to return
* @param action
* the action to use for the opened TX
*
* @return the realm with the given name
*
* @throws StrolchException
* if the {@link StrolchRealm} does not exist with the given name
*/
protected StrolchTransaction openUserTx(String action) throws StrolchException {
return this.container.getRealm(getCertificate()).openTx(getCertificate(), action);
}
/**
* Performs the given {@link SystemUserAction} as a system user with the given username. Returns the action for
* chaining calls
*
* @param username
* the name of the system user to perform the action as
* @param action
* the action to perform
*
* @return the action performed for chaining calls
*
* @throws PrivilegeException
*/
protected <V extends SystemUserAction> V runAs(String username, V action) throws PrivilegeException {
return this.container.getPrivilegeHandler().runAsSystem(username, action);
}
/**
* Performs the given {@link SystemUserAction} as the privileged system user
* {@link StrolchConstants#PRIVILEGED_SYSTEM_USER}. Returns the action for chaining calls
*
* @param action
* the action to perform
*
* @return the action performed for chaining calls
*
* @throws PrivilegeException
*/
protected <V extends SystemUserAction> V runPrivileged(V action) throws PrivilegeException {
return this.container.getPrivilegeHandler().runAsSystem(StrolchConstants.PRIVILEGED_SYSTEM_USER, action);
}
/**
* This method is final as it enforces that the argument is valid, and catches all exceptions and enforces that a
* service result is returned. A concrete implementation will implement the business logic in
* {@link #internalDoService(ServiceArgument)}
*/
@Override
public final U doService(T argument) {
if (isArgumentRequired() && argument == null) {
String msg = "Failed to perform service {0} because no argument was passed although it is required!"; //$NON-NLS-1$
msg = MessageFormat.format(msg, getClass());
logger.error(msg);
U result = getResultInstance();
result.setState(ServiceResultState.FAILED);
result.setMessage(msg);
return result;
}
try {
U serviceResult = internalDoService(argument);
if (serviceResult == null) {
String msg = "Service {0} is not properly implemented as it returned a null result!"; //$NON-NLS-1$
msg = MessageFormat.format(msg, this.getClass().getName());
throw new StrolchException(msg);
}
return serviceResult;
} catch (Exception e) {
U result = getResultInstance();
result.setState(ServiceResultState.FAILED);
result.setMessage(e.getMessage());
result.setThrowable(e);
return result;
}
}
/**
* Returns true if this Service requires an argument
*
* @return if true, then an argument must be set to execute the service. If the argument is missing, then the
* service execution fails immediately
*/
protected boolean isArgumentRequired() {
return true;
}
/**
* This method is called if the service execution fails and an instance of the expected {@link ServiceResult} is
* required to return to the caller
*
* @return an instance of the {@link ServiceResult} returned by this implementation
*/
protected abstract U getResultInstance();
/**
* Internal method to perform the {@link Service}. The implementor does not need to handle exceptions as this is
* done in the {@link #doService(ServiceArgument)} which calls this method
*
* @param arg
* the {@link ServiceArgument} containing the arguments to perform the concrete service
*
* @return a {@link ServiceResult} which denotes the execution state of this {@link Service}
*
* @throws Exception
* if something went wrong. The caller will catch and handle the {@link ServiceResult}
*/
protected abstract U internalDoService(T arg) throws Exception;
/**
* @see li.strolch.privilege.model.Restrictable#getPrivilegeName()
*/
@Override
public String getPrivilegeName() {
return Service.class.getName();
}
/**
* @see li.strolch.privilege.model.Restrictable#getPrivilegeValue()
*/
@Override
public Object getPrivilegeValue() {
return this.getClass().getName();
}
}