Checkpoint: Introducing authenticaion; re-orged some packages

This commit is contained in:
Darin Kelkhoff
2022-02-15 18:13:04 -06:00
parent b5e11176f6
commit 980f989779
28 changed files with 744 additions and 40 deletions

View File

@ -0,0 +1,33 @@
/*
* Copyright © 2021-2022. Kingsrook LLC <contact@kingsrook.com>. All Rights Reserved.
*/
package com.kingsrook.qqq.backend.core.actions;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.AbstractQRequest;
import com.kingsrook.qqq.backend.core.modules.QAuthenticationModuleDispatcher;
import com.kingsrook.qqq.backend.core.modules.interfaces.QAuthenticationModuleInterface;
/*******************************************************************************
**
*******************************************************************************/
public class ActionHelper
{
/*******************************************************************************
**
*******************************************************************************/
public static void validateSession(AbstractQRequest request) throws QException
{
QAuthenticationModuleDispatcher qAuthenticationModuleDispatcher = new QAuthenticationModuleDispatcher();
QAuthenticationModuleInterface authenticationModule = qAuthenticationModuleDispatcher.getQModule(request.getAuthenticationMetaData());
if(!authenticationModule.isSessionValid(request.getSession()))
{
throw new QException("Invalid session in request");
}
}
}

View File

@ -8,9 +8,8 @@ package com.kingsrook.qqq.backend.core.actions;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.delete.DeleteRequest;
import com.kingsrook.qqq.backend.core.model.actions.delete.DeleteResult;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.modules.QModuleDispatcher;
import com.kingsrook.qqq.backend.core.modules.interfaces.QModuleInterface;
import com.kingsrook.qqq.backend.core.modules.QBackendModuleDispatcher;
import com.kingsrook.qqq.backend.core.modules.interfaces.QBackendModuleInterface;
/*******************************************************************************
@ -24,11 +23,10 @@ public class DeleteAction
*******************************************************************************/
public DeleteResult execute(DeleteRequest deleteRequest) throws QException
{
QModuleDispatcher qModuleDispatcher = new QModuleDispatcher();
ActionHelper.validateSession(deleteRequest);
QBackendMetaData backend = deleteRequest.getBackend();
QModuleInterface qModule = qModuleDispatcher.getQModule(backend);
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQModule(deleteRequest.getBackend());
// todo pre-customization - just get to modify the request?
DeleteResult deleteResult = qModule.getDeleteInterface().execute(deleteRequest);
// todo post-customization - can do whatever w/ the result if you want

View File

@ -8,9 +8,8 @@ package com.kingsrook.qqq.backend.core.actions;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.insert.InsertRequest;
import com.kingsrook.qqq.backend.core.model.actions.insert.InsertResult;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.modules.QModuleDispatcher;
import com.kingsrook.qqq.backend.core.modules.interfaces.QModuleInterface;
import com.kingsrook.qqq.backend.core.modules.QBackendModuleDispatcher;
import com.kingsrook.qqq.backend.core.modules.interfaces.QBackendModuleInterface;
/*******************************************************************************
@ -24,11 +23,10 @@ public class InsertAction
*******************************************************************************/
public InsertResult execute(InsertRequest insertRequest) throws QException
{
QModuleDispatcher qModuleDispatcher = new QModuleDispatcher();
ActionHelper.validateSession(insertRequest);
QBackendMetaData backend = insertRequest.getBackend();
QModuleInterface qModule = qModuleDispatcher.getQModule(backend);
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQModule(insertRequest.getBackend());
// todo pre-customization - just get to modify the request?
InsertResult insertResult = qModule.getInsertInterface().execute(insertRequest);
// todo post-customization - can do whatever w/ the result if you want

View File

@ -25,6 +25,8 @@ public class MetaDataAction
*******************************************************************************/
public MetaDataResult execute(MetaDataRequest metaDataRequest) throws QException
{
ActionHelper.validateSession(metaDataRequest);
// todo pre-customization - just get to modify the request?
MetaDataResult metaDataResult = new MetaDataResult();

View File

@ -8,9 +8,8 @@ package com.kingsrook.qqq.backend.core.actions;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.query.QueryRequest;
import com.kingsrook.qqq.backend.core.model.actions.query.QueryResult;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.modules.QModuleDispatcher;
import com.kingsrook.qqq.backend.core.modules.interfaces.QModuleInterface;
import com.kingsrook.qqq.backend.core.modules.QBackendModuleDispatcher;
import com.kingsrook.qqq.backend.core.modules.interfaces.QBackendModuleInterface;
/*******************************************************************************
@ -24,11 +23,10 @@ public class QueryAction
*******************************************************************************/
public QueryResult execute(QueryRequest queryRequest) throws QException
{
QModuleDispatcher qModuleDispatcher = new QModuleDispatcher();
ActionHelper.validateSession(queryRequest);
QBackendMetaData backend = queryRequest.getBackend();
QModuleInterface qModule = qModuleDispatcher.getQModule(backend);
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQModule(queryRequest.getBackend());
// todo pre-customization - just get to modify the request?
QueryResult queryResult = qModule.getQueryInterface().execute(queryRequest);
// todo post-customization - can do whatever w/ the result if you want

View File

@ -35,6 +35,8 @@ public class RunFunctionAction
*******************************************************************************/
public RunFunctionResult execute(RunFunctionRequest runFunctionRequest) throws QException
{
ActionHelper.validateSession(runFunctionRequest);
///////////////////////////////////////////////////////
// todo - shouldn't meta-data validation catch this? //
///////////////////////////////////////////////////////
@ -109,6 +111,7 @@ public class RunFunctionAction
if(runFunctionRequest.getRecords() == null)
{
QueryRequest queryRequest = new QueryRequest(runFunctionRequest.getInstance());
queryRequest.setSession(runFunctionRequest.getSession());
queryRequest.setTableName(inputMetaData.getRecordListMetaData().getTableName());
// todo - handle this being async (e.g., http)

View File

@ -27,6 +27,8 @@ public class RunProcessAction
*******************************************************************************/
public RunProcessResult execute(RunProcessRequest runProcessRequest) throws QException
{
ActionHelper.validateSession(runProcessRequest);
///////////////////////////////////////////////////////
// todo - shouldn't meta-data validation catch this? //
///////////////////////////////////////////////////////

View File

@ -24,6 +24,8 @@ public class TableMetaDataAction
*******************************************************************************/
public TableMetaDataResult execute(TableMetaDataRequest tableMetaDataRequest) throws QException
{
ActionHelper.validateSession(tableMetaDataRequest);
// todo pre-customization - just get to modify the request?
TableMetaDataResult tableMetaDataResult = new TableMetaDataResult();

View File

@ -7,7 +7,9 @@ package com.kingsrook.qqq.backend.core.model.actions;
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.session.QSession;
/*******************************************************************************
@ -17,7 +19,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
public abstract class AbstractQRequest
{
protected QInstance instance;
// todo session
protected QSession session;
@ -41,7 +43,7 @@ public abstract class AbstractQRequest
// if this instance hasn't been validated yet, do so now //
// noting that this will also enrich any missing metaData //
////////////////////////////////////////////////////////////
if(! instance.getHasBeenValidated())
if(!instance.getHasBeenValidated())
{
try
{
@ -57,6 +59,16 @@ public abstract class AbstractQRequest
/*******************************************************************************
**
*******************************************************************************/
public QAuthenticationMetaData getAuthenticationMetaData()
{
return (instance.getAuthentication());
}
/*******************************************************************************
** Getter for instance
**
@ -76,4 +88,26 @@ public abstract class AbstractQRequest
{
this.instance = instance;
}
/*******************************************************************************
** Getter for session
**
*******************************************************************************/
public QSession getSession()
{
return session;
}
/*******************************************************************************
** Setter for session
**
*******************************************************************************/
public void setSession(QSession session)
{
this.session = session;
}
}

View File

@ -0,0 +1,164 @@
/*
* Copyright © 2021-2021. Kingsrook LLC <contact@kingsrook.com>. All Rights Reserved.
*/
package com.kingsrook.qqq.backend.core.model.metadata;
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonFilter;
/*******************************************************************************
** Meta-data to provide details of an authentication provider (e.g., google, saml,
** etc) within a qqq instance
**
*******************************************************************************/
public class QAuthenticationMetaData
{
private String name;
private String type;
@JsonFilter("secretsFilter")
private Map<String, String> values;
/*******************************************************************************
**
*******************************************************************************/
public String getValue(String key)
{
if(values == null)
{
return null;
}
return values.get(key);
}
/*******************************************************************************
**
*******************************************************************************/
public void setValue(String key, String value)
{
if(values == null)
{
values = new HashMap<>();
}
values.put(key, value);
}
/*******************************************************************************
**
*******************************************************************************/
public QAuthenticationMetaData withValue(String key, String value)
{
if(values == null)
{
values = new HashMap<>();
}
values.put(key, value);
return (this);
}
/*******************************************************************************
**
*******************************************************************************/
public String getName()
{
return name;
}
/*******************************************************************************
**
*******************************************************************************/
public void setName(String name)
{
this.name = name;
}
/*******************************************************************************
**
*******************************************************************************/
public QAuthenticationMetaData withName(String name)
{
this.name = name;
return (this);
}
/*******************************************************************************
** Getter for type
**
*******************************************************************************/
public String getType()
{
return type;
}
/*******************************************************************************
** Setter for type
**
*******************************************************************************/
public void setType(String type)
{
this.type = type;
}
/*******************************************************************************
**
*******************************************************************************/
public QAuthenticationMetaData withType(String type)
{
this.type = type;
return (this);
}
/*******************************************************************************
**
*******************************************************************************/
public Map<String, String> getValues()
{
return values;
}
/*******************************************************************************
**
*******************************************************************************/
public void setValues(Map<String, String> values)
{
this.values = values;
}
/*******************************************************************************
**
*******************************************************************************/
public QAuthenticationMetaData withVales(Map<String, String> values)
{
this.values = values;
return (this);
}
}

View File

@ -27,6 +27,8 @@ public class QInstance
@JsonIgnore
private Map<String, QBackendMetaData> backends = new HashMap<>();
private QAuthenticationMetaData authentication = null;
private Map<String, QTableMetaData> tables = new HashMap<>();
private Map<String, QProcessMetaData> processes = new HashMap<>();
@ -257,4 +259,24 @@ public class QInstance
}
/*******************************************************************************
** Getter for authentication
**
*******************************************************************************/
public QAuthenticationMetaData getAuthentication()
{
return authentication;
}
/*******************************************************************************
** Setter for authentication
**
*******************************************************************************/
public void setAuthentication(QAuthenticationMetaData authentication)
{
this.authentication = authentication;
}
}

View File

@ -0,0 +1,130 @@
/*
* Copyright © 2021-2022. Kingsrook LLC <contact@kingsrook.com>. All Rights Reserved.
*/
package com.kingsrook.qqq.backend.core.model.session;
import java.util.HashMap;
import java.util.Map;
/*******************************************************************************
**
*******************************************************************************/
public class QSession
{
private String idReference;
private QUser user;
// implementation-specific custom values
private Map<String, String> values;
/*******************************************************************************
** Getter for idReference
**
*******************************************************************************/
public String getIdReference()
{
return idReference;
}
/*******************************************************************************
** Setter for idReference
**
*******************************************************************************/
public void setIdReference(String idReference)
{
this.idReference = idReference;
}
/*******************************************************************************
** Getter for user
**
*******************************************************************************/
public QUser getUser()
{
return user;
}
/*******************************************************************************
** Setter for user
**
*******************************************************************************/
public void setUser(QUser user)
{
this.user = user;
}
/*******************************************************************************
**
*******************************************************************************/
public String getValue(String key)
{
if(values == null)
{
return null;
}
return values.get(key);
}
/*******************************************************************************
**
*******************************************************************************/
public void setValue(String key, String value)
{
if(values == null)
{
values = new HashMap<>();
}
values.put(key, value);
}
/*******************************************************************************
**
*******************************************************************************/
public QSession withValue(String key, String value)
{
if(values == null)
{
values = new HashMap<>();
}
values.put(key, value);
return (this);
}
/*******************************************************************************
**
*******************************************************************************/
public Map<String, String> getValues()
{
return values;
}
/*******************************************************************************
**
*******************************************************************************/
public void setValues(Map<String, String> values)
{
this.values = values;
}
}

View File

@ -0,0 +1,59 @@
/*
* Copyright © 2021-2022. Kingsrook LLC <contact@kingsrook.com>. All Rights Reserved.
*/
package com.kingsrook.qqq.backend.core.model.session;
/*******************************************************************************
**
*******************************************************************************/
public class QUser
{
private String idReference;
private String fullName;
/*******************************************************************************
** Getter for idReference
**
*******************************************************************************/
public String getIdReference()
{
return idReference;
}
/*******************************************************************************
** Setter for idReference
**
*******************************************************************************/
public void setIdReference(String idReference)
{
this.idReference = idReference;
}
/*******************************************************************************
** Getter for fullName
**
*******************************************************************************/
public String getFullName()
{
return fullName;
}
/*******************************************************************************
** Setter for fullName
**
*******************************************************************************/
public void setFullName(String fullName)
{
this.fullName = fullName;
}
}

View File

@ -0,0 +1,72 @@
/*
* Copyright © 2021-2021. Kingsrook LLC <contact@kingsrook.com>. All Rights Reserved.
*/
package com.kingsrook.qqq.backend.core.modules;
import java.util.HashMap;
import java.util.Map;
import com.kingsrook.qqq.backend.core.exceptions.QModuleDispatchException;
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationMetaData;
import com.kingsrook.qqq.backend.core.modules.interfaces.QAuthenticationModuleInterface;
/*******************************************************************************
** This class is responsible for loading an authentication module, by its name, and
** returning an instance.
**
** TODO - make this mapping runtime-bound, not pre-compiled in.
**
*******************************************************************************/
public class QAuthenticationModuleDispatcher
{
private Map<String, String> authenticationTypeToModuleClassNameMap;
/*******************************************************************************
**
*******************************************************************************/
public QAuthenticationModuleDispatcher()
{
authenticationTypeToModuleClassNameMap = new HashMap<>();
authenticationTypeToModuleClassNameMap.put("mock", "com.kingsrook.qqq.backend.core.modules.mock.MockAuthenticationModule");
authenticationTypeToModuleClassNameMap.put("fullyAnonymous", "com.kingsrook.qqq.backend.core.modules.defaults.FullyAnonymousAuthenticationModule");
authenticationTypeToModuleClassNameMap.put("TODO:google", "com.kingsrook.qqq.authentication.module.google.GoogleAuthenticationModule");
// todo - let user define custom type -> classes
}
/*******************************************************************************
**
*******************************************************************************/
public QAuthenticationModuleInterface getQModule(QAuthenticationMetaData authenticationMetaData) throws QModuleDispatchException
{
if(authenticationMetaData == null)
{
throw (new QModuleDispatchException("No authentication meta data defined."));
}
try
{
String className = authenticationTypeToModuleClassNameMap.get(authenticationMetaData.getType());
if(className == null)
{
throw (new QModuleDispatchException("Unrecognized authentication type [" + authenticationMetaData.getType() + "] in dispatcher."));
}
Class<?> moduleClass = Class.forName(className);
return (QAuthenticationModuleInterface) moduleClass.getDeclaredConstructor().newInstance();
}
catch(QModuleDispatchException qmde)
{
throw (qmde);
}
catch(Exception e)
{
throw (new QModuleDispatchException("Error getting authentication module of type: " + authenticationMetaData.getType(), e));
}
}
}

View File

@ -9,7 +9,7 @@ import java.util.HashMap;
import java.util.Map;
import com.kingsrook.qqq.backend.core.exceptions.QModuleDispatchException;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.modules.interfaces.QModuleInterface;
import com.kingsrook.qqq.backend.core.modules.interfaces.QBackendModuleInterface;
/*******************************************************************************
@ -19,7 +19,7 @@ import com.kingsrook.qqq.backend.core.modules.interfaces.QModuleInterface;
** TODO - make this mapping runtime-bound, not pre-compiled in.
**
*******************************************************************************/
public class QModuleDispatcher
public class QBackendModuleDispatcher
{
private Map<String, String> backendTypeToModuleClassNameMap;
@ -28,11 +28,11 @@ public class QModuleDispatcher
/*******************************************************************************
**
*******************************************************************************/
public QModuleDispatcher()
public QBackendModuleDispatcher()
{
backendTypeToModuleClassNameMap = new HashMap<>();
backendTypeToModuleClassNameMap.put("mock", "com.kingsrook.qqq.backend.core.modules.mock.MockModule");
backendTypeToModuleClassNameMap.put("rdbms", "com.kingsrook.qqq.backend.module.rdbms.RDBSMModule");
backendTypeToModuleClassNameMap.put("mock", "com.kingsrook.qqq.backend.core.modules.mock.MockBackendModule");
backendTypeToModuleClassNameMap.put("rdbms", "com.kingsrook.qqq.backend.module.rdbms.RDBMSBackendModule");
// todo - let user define custom type -> classes
}
@ -41,7 +41,7 @@ public class QModuleDispatcher
/*******************************************************************************
**
*******************************************************************************/
public QModuleInterface getQModule(QBackendMetaData backend) throws QModuleDispatchException
public QBackendModuleInterface getQModule(QBackendMetaData backend) throws QModuleDispatchException
{
try
{
@ -52,7 +52,7 @@ public class QModuleDispatcher
}
Class<?> moduleClass = Class.forName(className);
return (QModuleInterface) moduleClass.getDeclaredConstructor().newInstance();
return (QBackendModuleInterface) moduleClass.getDeclaredConstructor().newInstance();
}
catch(QModuleDispatchException qmde)
{
@ -60,7 +60,7 @@ public class QModuleDispatcher
}
catch(Exception e)
{
throw (new QModuleDispatchException("Error getting q backend module of type: " + backend.getType(), e));
throw (new QModuleDispatchException("Error getting backend module of type: " + backend.getType(), e));
}
}
}

View File

@ -0,0 +1,60 @@
/*
* Copyright © 2021-2022. Kingsrook LLC <contact@kingsrook.com>. All Rights Reserved.
*/
package com.kingsrook.qqq.backend.core.modules.defaults;
import java.util.Map;
import java.util.UUID;
import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.core.model.session.QUser;
import com.kingsrook.qqq.backend.core.modules.interfaces.QAuthenticationModuleInterface;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/*******************************************************************************
**
*******************************************************************************/
public class FullyAnonymousAuthenticationModule implements QAuthenticationModuleInterface
{
private static final Logger logger = LogManager.getLogger(FullyAnonymousAuthenticationModule.class);
/*******************************************************************************
**
*******************************************************************************/
@Override
public QSession createSession(Map<String, String> context)
{
QUser qUser = new QUser();
qUser.setIdReference("anonymous");
qUser.setFullName("Anonymous");
QSession qSession = new QSession();
if (context.get("sessionId") != null)
{
qSession.setIdReference(context.get("sessionId"));
}
else
{
qSession.setIdReference("Session:" + UUID.randomUUID());
}
qSession.setUser(qUser);
return (qSession);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public boolean isSessionValid(QSession session)
{
return (true);
}
}

View File

@ -0,0 +1,28 @@
/*
* Copyright © 2021-2021. Kingsrook LLC <contact@kingsrook.com>. All Rights Reserved.
*/
package com.kingsrook.qqq.backend.core.modules.interfaces;
import java.util.Map;
import com.kingsrook.qqq.backend.core.model.session.QSession;
/*******************************************************************************
** Interface that a QAuthenticationModule must implement.
**
*******************************************************************************/
public interface QAuthenticationModuleInterface
{
/*******************************************************************************
**
*******************************************************************************/
QSession createSession(Map<String, String> context);
/*******************************************************************************
**
*******************************************************************************/
boolean isSessionValid(QSession session);
}

View File

@ -6,13 +6,13 @@ package com.kingsrook.qqq.backend.core.modules.interfaces;
/*******************************************************************************
** Interface that a QModule must implement.
** Interface that a QBackendModule must implement.
**
** Note, methods all have a default version, which throws a 'not implement'
** Note, methods all have a default version, which throws a 'not implemented'
** exception.
**
*******************************************************************************/
public interface QModuleInterface
public interface QBackendModuleInterface
{
/*******************************************************************************
**

View File

@ -0,0 +1,65 @@
/*
* Copyright © 2021-2022. Kingsrook LLC <contact@kingsrook.com>. All Rights Reserved.
*/
package com.kingsrook.qqq.backend.core.modules.mock;
import java.util.Map;
import java.util.UUID;
import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.core.model.session.QUser;
import com.kingsrook.qqq.backend.core.modules.interfaces.QAuthenticationModuleInterface;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/*******************************************************************************
**
*******************************************************************************/
public class MockAuthenticationModule implements QAuthenticationModuleInterface
{
private static final Logger logger = LogManager.getLogger(MockAuthenticationModule.class);
/*******************************************************************************
**
*******************************************************************************/
@Override
public QSession createSession(Map<String, String> context)
{
QUser qUser = new QUser();
qUser.setIdReference("User:" + (System.currentTimeMillis() % 10_000));
qUser.setFullName("John Smith");
QSession qSession = new QSession();
qSession.setIdReference("Session:" + UUID.randomUUID());
qSession.setUser(qUser);
return (qSession);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public boolean isSessionValid(QSession session)
{
if(session == null)
{
logger.info("Session is null, which is not valid.");
return (false);
}
if(session.getValue("isInvalid") != null)
{
logger.info("Session contains the valid 'isInvalid', which is not valid.");
return (false);
}
return (true);
}
}

View File

@ -7,7 +7,7 @@ package com.kingsrook.qqq.backend.core.modules.mock;
import com.kingsrook.qqq.backend.core.modules.interfaces.DeleteInterface;
import com.kingsrook.qqq.backend.core.modules.interfaces.InsertInterface;
import com.kingsrook.qqq.backend.core.modules.interfaces.QModuleInterface;
import com.kingsrook.qqq.backend.core.modules.interfaces.QBackendModuleInterface;
import com.kingsrook.qqq.backend.core.modules.interfaces.QueryInterface;
@ -18,7 +18,7 @@ import com.kingsrook.qqq.backend.core.modules.interfaces.QueryInterface;
** tests over all the actions available through the QModuleInterface.
**
*******************************************************************************/
public class MockModule implements QModuleInterface
public class MockBackendModule implements QBackendModuleInterface
{
/*******************************************************************************
**