diff --git a/API.md b/API.md new file mode 100644 index 0000000..ab9a7ff --- /dev/null +++ b/API.md @@ -0,0 +1,173 @@ + + +# The Basics +At the heart of the ActiveStack API are [`SyncRequest`'s](src/main/java/com/percero/agents/sync/vo/SyncRequest.java) and [`SyncResponse`'s](src/main/java/com/percero/agents/sync/vo/SyncResponse.java). A specialized `SyncRequest` is sent to the ActiveStack Sync Engine and a `SyncResponse` is sent back the two being linked together by the `SyncResponse`.`correspondingMessageId`, which is the `ID` of the `SyncRequest`. + +## Login +Login is perhaps the most complicated of the API's in that it is meant to be generic and flexible enough to handle any type of authentication (OAuth, username/password, custom, etc). The main authentication engine is [`AuthService2`](src/main/java/com/percero/agents/auth/services/AuthService2.java). + +Login basically accepts some form of user credentials and returns a `UserID` and `ClientID` upon successful authentication. The `ClientID` is the main way that ActiveStack knows who the user is and also enables a single user to be simultaneously logged in on multipled devices. + +A successful login returns a [`UserToken`](src/main/java/com/percero/agents/auth/vo/UserToken.java) object that contains the [`User`](src/main/java/com/percero/agents/auth/vo/User.java), `clientId`, `deviceId`, and `token` (required for reauthentication). Typically, upon successful login, the app will issue a `findByExample` request for the class that represents the user in the application. For example, if `Person` is the class that represents the user, the app would issue a `findByExample` request with `theObject` payload: +``` +{ + "cn": "com.app.mo.Person", // Or whatever the actual class name of the `Person` object is + "userId": , // The `UserToken`.`User`.`ID` from the `AuthenticationResponse` +} +``` +This should result in the `Person` object identified by that User.ID to be returned by the ActiveStack SyncEngine. + +### [register](src/main/java/com/percero/agents/auth/services/AuthService2.java#L48) + +### [authenticate](src/main/java/com/percero/agents/auth/services/AuthService2.java#L79) +- Authenticates a user by their credentials. + - Request: [`AuthenticationRequest`](src/main/java/com/percero/agents/auth/vo/AuthenticationRequest.java) + - Response: [`AuthenticationResponse`](src/main/java/com/percero/agents/auth/vo/AuthenticationResponse.java) + - Parameters: + - `authProvider`: This is the name of the auth provider that will be used for authentication. The auth provider can either be built-in auth providers, or a custom defined auth provider. + - `credential`: This is a string that represents the user's credentials. Based on the authentication provider, this string is parsed accordingly to pull out the various parts of the credentials. + - Examples: + - [`BasicAuthCredential`](src/main/java/com/percero/agents/auth/vo/BasicAuthCredential.java): Handled by [`InMemoryAuthProvider`](src/main/java/com/percero/agents/auth/services/InMemoryAuthProvider.java) + - [`OAuthCredential`](src/main/java/com/percero/agents/auth/vo/OAuthCredential.java): Handled by [`GoogleAuthProvider`](src/main/java/com/percero/agents/auth/services/GoogleAuthProvider.java) + - AnonCredential: Handled by [`AnonAuthProvider`](src/main/java/com/percero/agents/auth/services/AnonAuthProvider.java) + +### [reauthenticate](src/main/java/com/percero/agents/auth/services/AuthService2.java#L83) +- Reauthenticates a user by a token (which is typically assigned upon successful authentication). + - Request: [`ReauthenticationRequest`](src/main/java/com/percero/agents/auth/vo/ReauthenticationRequest.java) + - Response: [`AuthenticationResponse`](src/main/java/com/percero/agents/auth/vo/AuthenticationResponse.java) + - Parameters: + - `authProvider`: This is the name of the auth provider that will be used for authentication. The auth provider can either be built-in auth providers, or a custom defined auth provider. + - `token`: This is the string token that was returned upon successful authentication. + +### [disconnect SyncEngine](src/main/java/com/percero/amqp/handlers/DisconnectHandler.java) +- Disconnects a client from the ActiveStack SyncEngine. This means that the client will no longer be pushed updates. Note that updates will be stored up for the client until they either reconnect or logout. + - Request: [`DisconnectRequest`](src/main/java/com/percero/agents/sync/vo/DisconnectRequest.java) + - Response: [`DisconnectResponse`](src/main/java/com/percero/agents/sync/vo/DisconnectResponse.java) + - Parameters: + - `userId` + - `clientId` + +### [disconnect Auth](https://github.com/ActiveStack/syncengine/blob/master/src/main/java/com/percero/agents/auth/services/AuthService.java#L706) +- Disconnects a client from ActiveStack Auth. + - Request: [`DisconnectRequest`](src/main/java/com/percero/agents/auth/vo/DisconnectRequest.java) + - Response: [`DisconnectResponse`](src/main/java/com/percero/agents/auth/vo/DisconnectResponse.java) + - Parameters: + - `userId` + - `clientId` + +### [logout](src/main/java/com/percero/amqp/handlers/LogoutHandler.java) +- Logs out a client from ActiveStack. This means that the client will no longer be notified of updates. Note that updates will NOT be stored up for the client. + - Request: [`LogoutRequest`](src/main/java/com/percero/agents/sync/vo/LogoutRequest.java) + - Response: null - Since the client has logged out, it is assumed they are not listening for any further messages, so no response is sent. + - Parameters: + - `userId` + - `clientId` + +## Core API + +- All `className` references assume that the corresponding class is part of the registered data model, meaning it is included in the `ActiveStack.Domain` module. + +### [connect](src/main/java/com/percero/amqp/handlers/ConnectHandler.java) +- Upon successful authentication, the ActiveStack Gateway will send a [`ConnectRequest`]() to the SyncEngine on behalf of the client. This is to let the SyncEngine know that this client has come online. +- NOTE: The client app itself is never aware of this `ConnectRequest`, it is handled automatically under the hood by the ActiveStack Gateway. However, the `ConnectResponse` IS sent to the client app so that it knows it is now connected to the SyncEngine and can start sending requests. + - Request: [`ConnectRequest`](src/main/java/com/percero/agents/sync/vo/ConnectRequest.java) + - Response: [`ConnectResponse`](src/main/java/com/percero/agents/sync/vo/ConnectResponse.java) + +### [reconnect](src/main/java/com/percero/amqp/handlers/ReconnectHandler.java) +- When a client loses connection to ActiveStack, it can send a [`ReconnectRequest`](src/main/java/com/percero/agents/sync/vo/ReconnectRequest.java) to the SyncEngine. This is to let the SyncEngine know that this client has come back online. + - Request: [`ReconnectRequest`](src/main/java/com/percero/agents/sync/vo/ReconnectRequest.java) + - Response: [`ReconnectResponse`](src/main/java/com/percero/agents/sync/vo/ReconnectResponse.java) + +### [findById](src/main/java/com/percero/amqp/handlers/FindByIdHandler.java) +- Retrieves the object identified by a `className` and an `ID`. The server will respond with a `findByIdResponse` containing either the result, or a NULL result indicating the specified object was not found, and registers the Client for any updates to that object. + - Request: [`FindByIdRequest`](https://github.com/ActiveStack/syncengine/blob/master/src/main/java/com/percero/agents/sync/vo/FindByIdRequest.java) + - Response: [`FindByIdResponse`](https://github.com/ActiveStack/syncengine/blob/master/src/main/java/com/percero/agents/sync/vo/FindByIdResponse.java) + - Parameters: + - `theClassName`: The name of the class + - `theClassId`: The ID of the object to find + - Note that the API should be able to handle inheritance. So "parentClass::ID" and "class::ID" should both return the same result (assuming that "class" inherits from "parentClass"). + +### [findByIds](src/main/java/com/percero/amqp/handlers/FindByIdsHandler.java) +- Retrieves a list of object identified by the `className` and a list of `ID`'s and registers the Client for any updates to those objects. + - Request: [`FindByIdsRequest`](https://github.com/ActiveStack/syncengine/blob/master/src/main/java/com/percero/agents/sync/vo/FindByIdsRequest.java) + - Response: [`FindByIdsResponse`](https://github.com/ActiveStack/syncengine/blob/master/src/main/java/com/percero/agents/sync/vo/FindByIdsResponse.java) + - Parameters: + - `theClassIdList`: [`ClassIDPairs`](src/main/java/com/percero/agents/sync/vo/ClassIDPairs.java) object which contains the `className` and a list of `ID`'s to retrieve + - Note that the API should be able to handle inheritance. So "parentClass::ID" and "class::ID" should both return the same result (assuming that "class" inherits from "parentClass"). + +### [getAllByName](src/main/java/com/percero/amqp/handlers/GetAllByNameHandler.java) +- Retrieves all objects of a particular class and registers the Client for any updates to those objects. + - Request: [`GetAllByNameRequest`](src/main/java/com/percero/agents/sync/vo/GetAllByNameRequest.java) + - Response: [`GetAllByNameResponse`](src/main/java/com/percero/agents/sync/vo/GetAllByNameResponse.java) + - Parameters: + - `theClassName`: The name of the class to get all objects. + - `pageSize` (optional): Number of items to return in result. + - `pageNumber` (optional): Desired page number + - `returnTotal` (optional): If set to `true`, returns the total number of objects to be returned. Typically used in the first call to determine exactly how many objects are expected. + +### [findByExample](src/main/java/com/percero/amqp/handlers/FindByExampleHandler.java) +- Retrieves all objects that match the supplied sample object and registers the Client for any updates to those objects. + - Request: [`FindByExampleRequest`](src/main/java/com/percero/agents/sync/vo/FindByExampleRequest.java) + - Response: [`FindByExampleResponse`](src/main/java/com/percero/agents/sync/vo/FindByExampleResponse.java) + - Parameters: + - `theObject`: A sample object of the domain model. Fields on the object that are set will be included as part of the filter criteria + +### [putObject](src/main/java/com/percero/amqp/handlers/PutObjectHandler.java) +- Updates an existing object + - Request: [`PutRequest`](src/main/java/com/percero/agents/sync/vo/PutRequest.java) + - Response: [`PutResponse`](src/main/java/com/percero/agents/sync/vo/PutResponse.java) + - Parameters: + - `theObject` + - `putTimestamp` (optional): The time this object was updated. This is used for conflict resolution + - `transId` (optional): A client defined transaction ID. Mostly used for tracking of updates, does NOT enforce a database transaction. + +### [createObject](src/main/java/com/percero/amqp/handlers/CreateObjectHandler.java) +- Creates a new object + - Request: [`CreateRequest`](src/main/java/com/percero/agents/sync/vo/CreateRequest.java) + - Response: [`CreateResponse`](src/main/java/com/percero/agents/sync/vo/CreateResponse.java) + - Parameters: + - `theObject`: The object to be created. If the object does NOT contain an ID, the ActiveStack Sync Engine will create one. + +### [removeObject](src/main/java/com/percero/amqp/handlers/RemoveObjectHandler.java) +- Removes an existing object + - Request: [`RemoveRequest`](src/main/java/com/percero/agents/sync/vo/RemoveRequest.java) + - Response: [`RemoveResponse`](src/main/java/com/percero/agents/sync/vo/RemoveResponse.java) + - Parameters: + - `removePair`: A [`ClassIDPair`](src/main/java/com/percero/agents/sync/vo/ClassIDPair.java) identifying the ID and class name of the object to be removed + +### [getChangeWatcher](src/main/java/com/percero/amqp/handlers/GetChangeWatcherHandler.java) +- Retrieves the value of a ChangeWatcher and registers the Client for any updates to that value. + - Request: [`PushCWUpdateRequest`](https://github.com/ActiveStack/syncengine/blob/master/src/main/java/com/percero/agents/sync/vo/PushCWUpdateRequest.java) + - Response: [`PushCWUpdateResponse`](https://github.com/ActiveStack/syncengine/blob/master/src/main/java/com/percero/agents/sync/vo/PushCWUpdateResponse.java) + - Parameters: + - `classIdPair`: A [`ClassIDPair`](src/main/java/com/percero/agents/sync/vo/ClassIDPair.java) identifying the ID and class name of the object that the ChangeWatcher hangs off of. + - `fieldName`: The name of the field that represents the ChangeWatcher. + - `params` (optional): An array of strings that represent the parameters that uniquely identify the ChangeWatcher. + +### [runServerProcess](src/main/java/com/percero/amqp/handlers/RunProcessHandler.java) +- Runs a custom server process. This process can be a custom piece of code, a defined HTTP process, a defined SQL stored procedure, or some other defined Connector. + - Request: [`RunServerProcessRequest`](https://github.com/ActiveStack/syncengine/blob/master/src/main/java/com/percero/agents/sync/vo/RunServerProcessRequest.java) + - Response: [`RunServerProcessResponse`](https://github.com/ActiveStack/syncengine/blob/master/src/main/java/com/percero/agents/sync/vo/RunServerProcessResponse.java) + - Parameters: + - `queryName`: The name of the process. To use a specific Connector (such as 'HTTP' or 'SQL_PROC' for database stored procedures), prefix the operation name of the Connector name and a ":". Example: "HTTP:fetchDataFromHttpEndpoint" + - `queryArguments` (optional): Any required parameters for the server process. Typically, this is passed as some sort of map (parameterName -> parameterValue) + + +## Push Notifications from SyncEngine +This is really where the real-time aspect of ActiveStack comes into play. The main point here is that clients are notified of updates to objects that they are currently interested in. It is up to the client SDK to respond appropriately to these update notifications. + +### `pushUpdate` +Sent whenever an object has been updated for which a client has registered to receive updates. +- [PutObject Pushes](src/main/java/com/percero/agents/sync/helpers/PostPutHelper.java#L173) +- [Client Reconnect Push Updates](src/main/java/com/percero/agents/sync/services/SyncAgentService.java#L1357) +- [Change Watcher Pushes](src/main/java/com/percero/agents/sync/cw/DerivedValueChangeWatcherHelper.java#L326) +- Response: [`PushUpdateResponse`](src/main/java/com/percero/agents/sync/vo/PushUpdateResponse.java) + +### `deleteUpdate` +Sent whenever an object has been deleted for which a client has registered to receive updates. +- [RemoveObject Pushes](src/main/java/com/percero/agents/sync/helpers/PostDeleteHelper.java#L103) +- [Client Reconnect Push Deletes](src/main/java/com/percero/agents/sync/services/SyncAgentService.java#L1423) +- Response: [`PushDeleteResponse`](src/main/java/com/percero/agents/sync/vo/PushDeleteResponse.java) + + + diff --git a/AUTH_PROVIDER.md b/AUTH_PROVIDER.md new file mode 100644 index 0000000..43dbf02 --- /dev/null +++ b/AUTH_PROVIDER.md @@ -0,0 +1,180 @@ +# Auth Provider + +## Basic Auth Provider +Out of the box, ActiveStack provides a basic Auth Provider which uses simple username/password stored in the ActiveStack Auth database. + +By default, the Basic Auth Provider is disabled. In order to enable it, you must add the following line to your [env.properties](src/main/resources/env.properties.sample) file. +``` +auth.basic.enabled=true +``` + +## Custom Auth Provider +ActiveStack allows you to define your own Auth Provider. Follow these steps to create your own Custom Auth Provider. + +- Create a new Custom Auth Provider Factory class. This class is setup to be autowired by spring. On PostConstruct, this Factory creates a new instance of a CustomAuthProvider and registers it with the ActiveStack AuthProviderRegistry. +``` +@Component +public class CustomAuthProviderFactory { + + private static Logger logger = Logger.getLogger(CustomAuthProviderFactory.class); + + /** + * This is the ActiveStack Auth Provider registry. Auth Providers are + * identifed by their ID. This ID is passed from the client when attempting + * to authorize/register. It is advised to namespace Auth Provider ID's. + */ + @Autowired + AuthProviderRegistry authProviderRegistry; + + @PostConstruct + public void init(){ + // Depending on the details of the Auth Provider, you need to inject the appropriate resources. + CustomAuthProvider provider = new CustomAuthProvider(); + authProviderRegistry.addProvider(provider); + logger.info("CustomAuth:.............ENABLED"); + } + +} +``` + +- Create a new Custom Auth Provider class. This class does the actual work of authorizing and registering users. It must implement the [IAuthProvider](src/main/java/com/percero/agents/auth/services/IAuthProvider.java) class, which defines these three functions: + - `getID()`: Must return the UNIQUE ID of this Auth Provider. It is recommended to name space any custom Auth Providers just to make sure there is no name collision. + - `authorize(String credentialString)`: This is the function that is called to authorize a user. + - The format of `credentialString` is completely customizable -- as long as the client app and server Auth Provider agree on the format. There is a `BasicAuthCredential` defined as part of ActiveStack that can de-serialize the String `username:password` or `{"username":""}`. + - `register(String credentialString)`: This is the function that is called to register a user. Note that an Auth Provider does NOT need to support this functionality. If an Auth Provider does NOT support this functionality, the response should simply be an `AuthProviderResponse` with a failed result. + - The format of `credentialString` is completely customizable -- as long as the client app and server Auth Provider agree on the format. There is a `BasicAuthCredential` defined as part of ActiveStack that can de-serialize the String `username:password` or `{"username":""}`. +``` +public class CustomAuthProvider implements IAuthProvider { + + private static Logger logger = Logger.getLogger(CustomAuthProvider.class); + + /** + * Auth Providers are identifed by their ID. This ID is passed from the + * client when attempting to authorize/register. It is advised to namespace + * Auth Provider ID's. + */ + public static final String ID = "my_app:custom"; + + public CustomAuthProvider() { + // Nothing to do... + } + + public String getID() { + return ID; + } + + // This is our credential store for our custom auth. It is just a map of username/password combinations. + private Map registeredUsers = new ConcurrentHashMap(); + + + /** + * The credential string can be in any format - it just needs to be an + * agreed upon format between the client and the Auth Provider. Since this + * is a custom Auth Provider, the developer is free to choose any format + * they desire. In this case, we are using the BasicAuthCredential JSON + * format. + * + * @param credentialString + * - JSON String in `{"username":, "password": + * }` format + * @return + */ + public AuthProviderResponse authenticate(String credentialString) { + AuthProviderResponse response = new AuthProviderResponse(); + + BasicAuthCredential cred = BasicAuthCredential.fromJsonString(credentialString); + + logger.debug("Autheticating user " + cred.getUsername()); + + // To authorize, we simply check if the user name is in the `registeredUsers` map. + if (cred.getUsername() != null && cred.getPassword().equals(registeredUsers.get(cred.getUsername().toLowerCase()))) { + logger.debug("AUTH SUCCESS: " + cred.getUsername()); + response.authCode = AuthCode.SUCCESS; + + // Now put together the ServiceUser. + response.serviceUser = getServiceUser(cred.getUsername()); + } + else { + logger.debug("AUTH FAILURE: " + cred.getUsername()); + response.authCode = AuthCode.UNAUTHORIZED; + } + + return response; + } + + /* (non-Javadoc) + * @see com.percero.agents.auth.services.IAuthProvider#register(java.lang.String) + */ + /** + * Registers a user with this Auth Provider. Note that Auth Providers do NOT + * need to support this method. If an Auth Provider does NOT support this + * method, then the AuthProviderResponse would simply be set to + * failed/rejected. + * + * Similar to authorize, the `registrationString` can be any format that the + * developer desires, so long as the client and this Auth Provider agree on + * the format. In this case, we are going to use the BasicAuthCredential + * JSON format. + * + * @param registrationString + * @return + */ + public AuthProviderResponse register(String registrationString) { + AuthProviderResponse response = new AuthProviderResponse(); + BasicAuthCredential cred = BasicAuthCredential.fromJsonString(registrationString); + + logger.debug("Registering user " + cred.getUsername()); + + if (StringUtils.hasText(cred.getUsername()) && StringUtils.hasText(cred.getPassword())) { + registeredUsers.put(cred.getUsername().toLowerCase(), cred.getPassword()); + + response.authCode = AuthCode.SUCCESS; + // Now put together the ServiceUser. + response.serviceUser = getServiceUser(cred.getUsername()); + } + else { + response.authCode = AuthCode.FAILURE; + } + + return response; + } + + /** + * Builds up a ServiceUser object that corresponds to the userName. For this + * example, we always fill it with the same data. + * + * @return + */ + protected ServiceUser getServiceUser(String userName) { + // Now put together the ServiceUser. + ServiceUser serviceUser = new ServiceUser(); + serviceUser.setAuthProviderID(getID()); + serviceUser.setFirstName("My"); + serviceUser.setLastName("Name"); + serviceUser.setId(userName); // The unique identifier for this Auth Provider for this user. + serviceUser.setEmails(new ArrayList()); + serviceUser.setIdentifiers(new ArrayList()); + + serviceUser.getEmails().add("myusername@mail.com"); + + // Add the email address as an identifier. This is typically used to + // link together a single user across multiple Auth Providers. + ServiceIdentifier emailServiceIdentifier = new ServiceIdentifier("email", "myusername@mail.com"); + serviceUser.getIdentifiers().add(emailServiceIdentifier); + + // Add the user name as another unique identifier. + ServiceIdentifier userNameServiceIdentifier = new ServiceIdentifier(getID(), userName); + serviceUser.getIdentifiers().add(userNameServiceIdentifier); + + // We are creating an Access Token for this service user. + serviceUser.setAccessToken(UUID.randomUUID().toString()); + + // If role names are handled by the Auth Provider, then we set this to `TRUE`, otherwise we set it to `FALSE`. + serviceUser.setAreRoleNamesAccurate(false); + + return serviceUser; + } + + +} +``` diff --git a/README.md b/README.md index c72933d..9dab276 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,16 @@ ## Authentication -ActiveStack provides several authentication mechanisms straight out of the box: anonymous, -Google and File based. We call these "Auth Providers". +ActiveStack provides several authentication mechanisms straight out of the box: Basic, anonymous, +Google and File based. We call these "[Auth Providers](AUTH_PROVIDER.md)". ### Usage +#### 'register' + + +#### 'authenticate' + In order to authenticate against one of these authentication options you'll need to send an 'authenticate' message to the server with content body looking something like: diff --git a/pom.xml b/pom.xml index 8cb767e..1119398 100644 --- a/pom.xml +++ b/pom.xml @@ -7,13 +7,13 @@ org.activestack syncengine - 1.1.59-SNAPSHOT + 1.1.67-SNAPSHOT 3.2.4.RELEASE 1.6.11 - 1.19.0 - 1.19.0 + 1.21.0 + 1.21.0 1.9.5 @@ -28,24 +28,12 @@ aspectjweaver ${aspectj.version} - - junit - junit - 4.8.2 - test - + - org.mockito - mockito-core - ${mockito-core.version} - test + org.codehaus.jackson + jackson-mapper-asl + 1.9.13 - - - org.codehaus.jackson - jackson-mapper-asl - 1.9.2 - org.hibernate hibernate @@ -126,12 +114,6 @@ boon 0.26 - - junit - junit - 4.8.2 - test - org.springframework @@ -160,18 +142,6 @@ spring-orm ${spring.version} - - org.springframework - spring-test - ${spring.version} - test - - - commons-logging - commons-logging - - - org.springframework spring-tx @@ -229,11 +199,6 @@ log4j 1.2.16 - - - - - commons-dbcp commons-dbcp @@ -252,10 +217,7 @@ com.google.apis google-api-services-oauth2 - - v2-rev78-1.19.0 + v2-rev107-1.21.0 com.google.http-client @@ -270,7 +232,7 @@ com.google.apis google-api-services-admin-directory - directory_v1-rev42-1.19.0 + directory_v1-rev64-1.21.0 com.restfb @@ -344,6 +306,30 @@ + + junit + junit + 4.8.2 + test + + + org.mockito + mockito-core + ${mockito-core.version} + test + + + org.springframework + spring-test + ${spring.version} + test + + + commons-logging + commons-logging + + + com.h2database h2 diff --git a/src/main/java/com/percero/agents/auth/helpers/AccountHelper.java b/src/main/java/com/percero/agents/auth/helpers/AccountHelper.java index 7febf4d..1bcff8c 100644 --- a/src/main/java/com/percero/agents/auth/helpers/AccountHelper.java +++ b/src/main/java/com/percero/agents/auth/helpers/AccountHelper.java @@ -10,6 +10,7 @@ import com.percero.agents.sync.metadata.*; import com.percero.agents.sync.services.IDataProviderManager; import com.percero.agents.sync.services.ISyncAgentService; +import com.percero.agents.sync.vo.BaseDataObject; import com.percero.agents.sync.vo.Client; import com.percero.framework.bl.IManifest; import com.percero.framework.bl.ManifestHelper; @@ -564,7 +565,7 @@ public void setupUserRoles(String userId, List serviceUserList) thr if (!isInaccurateList && !serviceUserRoleExists) { log.warn("Deleting role " + nextUserRole.getRoleName() + " for " + userId); - syncAgentService.systemDeleteObject(nextUserRole, null, true, new HashSet()); + syncAgentService.systemDeleteObject(BaseDataObject.toClassIdPair(nextUserRole), null, true); } else updatedUserRoles.add(nextUserRole); } diff --git a/src/main/java/com/percero/agents/auth/services/AnonAuthProvider.java b/src/main/java/com/percero/agents/auth/services/AnonAuthProvider.java index 6346ad4..483a1b8 100644 --- a/src/main/java/com/percero/agents/auth/services/AnonAuthProvider.java +++ b/src/main/java/com/percero/agents/auth/services/AnonAuthProvider.java @@ -28,6 +28,12 @@ public String getID() { return ID; } + public AuthProviderResponse register(String credential) { + AuthProviderResponse result = new AuthProviderResponse(); + result.authCode = AuthCode.FORBIDDEN; + return result; + } + public AuthProviderResponse authenticate(String credential) { AuthProviderResponse result = new AuthProviderResponse(); result.authCode = AuthCode.SUCCESS; diff --git a/src/main/java/com/percero/agents/auth/services/AuthException.java b/src/main/java/com/percero/agents/auth/services/AuthException.java new file mode 100644 index 0000000..3e8c98d --- /dev/null +++ b/src/main/java/com/percero/agents/auth/services/AuthException.java @@ -0,0 +1,49 @@ +package com.percero.agents.auth.services; + +public class AuthException extends Exception { + + public static final String DATA_ERROR = "dataError"; + public static final String DUPLICATE_USER_NAME = "duplicateUserName"; + public static final String INVALID_USER_IDENTIFIER = "invalidUserIdentifier"; + public static final String INVALID_USER_PASSWORD = "invalidUserPassword"; + public static final String INVALID_DATA = "invalidData"; + + public AuthException() { + // TODO Auto-generated constructor stub + } + + private String detail = ""; + public void setDetail(String detail) { + this.detail = detail; + } + public String getDetail() { + return detail; + } + + public AuthException(String message) { + super(message); + } + + public AuthException(String message, String detail) { + super(message); + this.detail = detail; + } + + public AuthException(Throwable cause) { + super(cause); + } + + public AuthException(String message, Throwable cause) { + super(message, cause); + } + + public AuthException(String message, String detail, Throwable cause) { + super(message, cause); + this.detail = detail; + } + + public AuthException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + +} diff --git a/src/main/java/com/percero/agents/auth/services/AuthProviderRegistry.java b/src/main/java/com/percero/agents/auth/services/AuthProviderRegistry.java index baaecb0..ba76ebc 100644 --- a/src/main/java/com/percero/agents/auth/services/AuthProviderRegistry.java +++ b/src/main/java/com/percero/agents/auth/services/AuthProviderRegistry.java @@ -31,11 +31,11 @@ public void setProviders(Collection providers){ * Add a provider to the registry * @param provider */ - public void addProvider(IAuthProvider provider){ + public IAuthProvider addProvider(IAuthProvider provider){ if(providerMap.containsKey(provider.getID())) logger.warn("Non-unique auth provider ID: "+provider.getID()); - providerMap.put(provider.getID().toLowerCase(), provider); + return providerMap.put(provider.getID().toLowerCase(), provider); } /** diff --git a/src/main/java/com/percero/agents/auth/services/AuthService.java b/src/main/java/com/percero/agents/auth/services/AuthService.java index 71b09bf..93d93c8 100644 --- a/src/main/java/com/percero/agents/auth/services/AuthService.java +++ b/src/main/java/com/percero/agents/auth/services/AuthService.java @@ -646,9 +646,15 @@ private UserToken loginUserAccount(UserAccount theUserAccount, String clientId, * @see com.com.percero.agents.auth.services.IAuthService#logoutUser(java.lang.String, java.lang.String, java.lang.String) */ public Boolean logoutUser(String aUserId, String aToken, String aClientId) { + Set clientIds = new HashSet(1); + clientIds.add(aClientId); + return logoutUser(aUserId, aToken, clientIds); + } + + public Boolean logoutUser(String aUserId, String aToken, Set clientIds) { Boolean result = false; boolean validUser = StringUtils.hasText(aUserId); - boolean validClient = StringUtils.hasText(aClientId); + boolean validClient = clientIds != null && !clientIds.isEmpty(); boolean validToken = StringUtils.hasText(aToken); // If neither a valid user or a valid client, then no one to logout. @@ -661,17 +667,16 @@ public Boolean logoutUser(String aUserId, String aToken, String aClientId) { // Match EITHER the ClientID OR the Token if (validClient && validToken) { - log.debug("Logging out Client: " + aClientId + ", Token: " + aToken); - deleteUserTokenSql += " (clientId=:clientId OR token=:token) "; + log.debug("Logging out Client(s): " + StringUtils.collectionToCommaDelimitedString(clientIds) + " / Token: " + aToken); + deleteUserTokenSql += " (clientId IN (" + StringUtils.collectionToDelimitedString(clientIds, ",", "\"", "\"") + ") OR token=:token) "; } else if (validToken) { - log.debug("Logging out Token: " + aToken); log.debug("Logging out Token: " + aToken); deleteUserTokenSql += " token=:token "; } else if (validClient) { - log.debug("Logging out Client: " + aClientId); - deleteUserTokenSql += " clientId=:clientId "; + log.debug("Logging out Client(s): " + StringUtils.collectionToCommaDelimitedString(clientIds)); + deleteUserTokenSql += " clientId IN (" + StringUtils.collectionToDelimitedString(clientIds, ",", "\"", "\"") + ") "; } else if (validUser) { // This will log out ALL of the User's devices, logging them out completely. @@ -687,13 +692,12 @@ else if (validUser) { if (validClient && validToken) { deleteQuery.setString("token", aToken); - deleteQuery.setString("clientId", aClientId); } else if (validToken) { deleteQuery.setString("token", aToken); } else if (validClient) { - deleteQuery.setString("clientId", aClientId); + // Do nothing. } else if (validUser) { deleteQuery.setString("user_ID", aUserId); @@ -904,75 +908,60 @@ public ServiceUser getServiceProviderServiceUser(String accessToken, String refr ServiceUser serviceUser = null; try { - - if (anonAuthEnabled && StringUtils.hasText(anonAuthCode)) { - if (refreshToken != null && refreshToken.equals(anonAuthCode)) { - serviceUser = new ServiceUser(); - serviceUser.setFirstName("ANON"); - serviceUser.setLastName("ANON"); - serviceUser.setId("ANON"); - serviceUser.setAuthProviderID(AuthProvider.ANON.toString()); - serviceUser.setRefreshToken(anonAuthCode); - - List roles = new ArrayList(); - String[] roleNames = anonAuthRoleNames.split(","); - for(int i = 0; i < roleNames.length; i++) { - if (roleNames[i] != null && !roleNames[i].isEmpty()) - roles.add(roleNames[i]); - } - serviceUser.setRoleNames(roles); - serviceUser.setAreRoleNamesAccurate(true); - return serviceUser; - } - } - - /**if (AuthProvider.LINKEDIN.equals(authProviderID)) { - // LinkedInHelper linkedInHelper = new LinkedInHelper(); - // ServiceUser liServiceUser = linkedInHelper.getServiceUser( - // svcOauth.getAppKey(), svcOauthSecret.getAppToken(), - // accessToken, - // refreshToken, - // svcOauth.getServiceApplication().getAppDomain()); - // serviceUser = liServiceUser; - } - else */if (AuthProvider.FACEBOOK.equals(authProviderID)) { + if (AuthProvider.FACEBOOK.name().equalsIgnoreCase(authProviderID)) { ServiceUser fbServiceUser = facebookHelper.getServiceUser( accessToken, accountId); serviceUser = fbServiceUser; } - else if (AuthProvider.GOOGLE.equals(authProviderID)) { + else if (AuthProvider.GOOGLE.name().equalsIgnoreCase(authProviderID)) { ServiceUser glServiceUser = googleHelper.authenticateAccessToken(accessToken, refreshToken, accountId); //ServiceUser glServiceUser = googleHelper.retrieveServiceUser(accountId); serviceUser = glServiceUser; } - else if (AuthProvider.GITHUB.equals(authProviderID)) { + else if (AuthProvider.GITHUB.name().equalsIgnoreCase(authProviderID)) { ServiceUser glServiceUser = githubHelper.getServiceUser(accessToken, refreshToken); serviceUser = glServiceUser; } - else if (AuthProvider.LINKEDIN.equals(authProviderID)) { + else if (AuthProvider.LINKEDIN.name().equalsIgnoreCase(authProviderID)) { ServiceUser liServiceUser = linkedInHelper.getServiceUser(accessToken, refreshToken); serviceUser = liServiceUser; } - else if(AuthProvider.ANON.equals(authProviderID)){ - serviceUser = new ServiceUser(); - serviceUser.setEmails(new ArrayList()); - serviceUser.getEmails().add("blargblarg@com.percero.com"); - serviceUser.setAccessToken("blargblarg"); - serviceUser.setFirstName("ANONYMOUS"); - serviceUser.setId("ANONYMOUS"); - serviceUser.setAuthProviderID(AuthProvider.ANON.toString()); + else if(AuthProvider.ANON.name().equalsIgnoreCase(authProviderID)){ + // We only allow anonymous if anonAuthEnabled is TRUE AND the anonAuthCode matches the refresh token. + if (anonAuthEnabled && StringUtils.hasText(anonAuthCode)) { + if (refreshToken != null && refreshToken.equals(anonAuthCode)) { + serviceUser = new ServiceUser(); + serviceUser.setFirstName("ANON"); + serviceUser.setLastName("ANON"); + serviceUser.setId("ANON"); + serviceUser.setAuthProviderID(AuthProvider.ANON.toString()); + serviceUser.setRefreshToken(anonAuthCode); + + List roles = new ArrayList(); + String[] roleNames = anonAuthRoleNames != null ? anonAuthRoleNames.split(",") : new String[0]; + for(int i = 0; i < roleNames.length; i++) { + if (roleNames[i] != null && !roleNames[i].isEmpty()) + roles.add(roleNames[i]); + } + serviceUser.setRoleNames(roles); + serviceUser.setAreRoleNamesAccurate(true); + } + } } - else - { + else { log.warn("ServiceProvider not yet supported: " + authProviderID); } } catch (Exception e) { - e.printStackTrace(); + log.error("Error getting Service User from Auth Provider", e); } - if (serviceUser != null) + if (serviceUser != null) { + // In case the client has used a different casing of the Auth + // Provider ID, we want to return the exact same casing. + // Ex: LinkedIn vs. linkedIn serviceUser.setAuthProviderID(authProviderID); + } return serviceUser; } diff --git a/src/main/java/com/percero/agents/auth/services/AuthService2.java b/src/main/java/com/percero/agents/auth/services/AuthService2.java index 298648f..eb1dd25 100644 --- a/src/main/java/com/percero/agents/auth/services/AuthService2.java +++ b/src/main/java/com/percero/agents/auth/services/AuthService2.java @@ -1,28 +1,58 @@ package com.percero.agents.auth.services; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.ServiceConfigurationError; +import java.util.UUID; + +import org.apache.log4j.Logger; +import org.hibernate.Criteria; +import org.hibernate.NonUniqueResultException; +import org.hibernate.Query; +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.Transaction; +import org.hibernate.criterion.Restrictions; +import org.hibernate.exception.LockAcquisitionException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + import com.percero.agents.auth.helpers.RoleHelper; import com.percero.agents.auth.helpers.UserAnchorHelper; import com.percero.agents.auth.helpers.UserIdentifierHelper; import com.percero.agents.auth.hibernate.AssociationExample; import com.percero.agents.auth.hibernate.AuthHibernateUtils; import com.percero.agents.auth.hibernate.BaseDataObjectPropertySelector; -import com.percero.agents.auth.vo.*; +import com.percero.agents.auth.vo.AuthProviderResponse; +import com.percero.agents.auth.vo.AuthenticationRequest; +import com.percero.agents.auth.vo.AuthenticationResponse; +import com.percero.agents.auth.vo.IUserAnchor; +import com.percero.agents.auth.vo.IUserIdentifier; +import com.percero.agents.auth.vo.IUserRole; +import com.percero.agents.auth.vo.ReauthenticationRequest; +import com.percero.agents.auth.vo.ServiceIdentifier; +import com.percero.agents.auth.vo.ServiceUser; +import com.percero.agents.auth.vo.User; +import com.percero.agents.auth.vo.UserAccount; +import com.percero.agents.auth.vo.UserIdentifier; +import com.percero.agents.auth.vo.UserToken; import com.percero.agents.sync.exceptions.SyncDataException; -import com.percero.agents.sync.metadata.*; +import com.percero.agents.sync.metadata.EntityImplementation; +import com.percero.agents.sync.metadata.IMappedClassManager; +import com.percero.agents.sync.metadata.MappedClass; +import com.percero.agents.sync.metadata.MappedClassManagerFactory; +import com.percero.agents.sync.metadata.PropertyImplementation; +import com.percero.agents.sync.metadata.PropertyImplementationParam; +import com.percero.agents.sync.metadata.RelationshipImplementation; import com.percero.agents.sync.services.ISyncAgentService; +import com.percero.agents.sync.vo.BaseDataObject; import com.percero.framework.bl.IManifest; import com.percero.framework.bl.ManifestHelper; import com.percero.framework.vo.IPerceroObject; -import org.apache.log4j.Logger; -import org.hibernate.*; -import org.hibernate.criterion.Restrictions; -import org.hibernate.exception.LockAcquisitionException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; - -import java.lang.reflect.InvocationTargetException; -import java.util.*; /** * This class handles AuthenticationRequest type authentication @@ -43,13 +73,136 @@ public class AuthService2 { @Autowired IManifest manifest; + + + /** + * Pre Register Handlers are handlers that are called BEFORE `register`. + * They allow for any custom hooks/logic that may need to be run before + * register is called. If an AuthException is thrown, the register request + * is cancelled. + * + * @param preRegisterHandler + */ + protected List preRegisterHandlers = new ArrayList(); + public void addPreRegisterHandler(IPreRegisterHandler preRegisterHandler) { + preRegisterHandlers.add(preRegisterHandler); + } + + /** + * Post Register Handlers are handlers that are called AFTER `register`. + * They allow for any custom hooks/logic that may need to be run after + * register is called. + * + * @param postRegisterHandler + */ + protected List postRegisterHandlers = new ArrayList(); + public void addPostRegisterHandler(IPostRegisterHandler postRegisterHandler) { + postRegisterHandlers.add(postRegisterHandler); + } + /** + * Pre Authenticate Handlers are handlers that are called BEFORE `authenticate`. + * They allow for any custom hooks/logic that may need to be run before + * authenticate is called. If an AuthException is thrown, the authenticate request + * is cancelled. + * + * @param preAuthenticateHandler + */ + protected List preAuthenticateHandlers = new ArrayList(); + public void addPreAuthenticateHandler(IPreAuthenticateHandler preAuthenticateHandler) { + preAuthenticateHandlers.add(preAuthenticateHandler); + } + + /** + * Post Authenticate Handlers are handlers that are called AFTER `authenticate`. + * They allow for any custom hooks/logic that may need to be run after + * authenticate is called. + * + * @param postAuthenticateHandler + */ + protected List postAuthenticateHandlers = new ArrayList(); + public void addPostAuthenticateHandler(IPostAuthenticateHandler postAuthenticateHandler) { + postAuthenticateHandlers.add(postAuthenticateHandler); + } + + + /** + * Registers a new user with the selected AuthProvider (if supported by the AuthProvider). + * @param request + * @return + * @throws IllegalArgumentException + */ + public AuthenticationResponse register(AuthenticationRequest request) throws IllegalArgumentException{ + if(!authProviderRegistry.hasProvider(request.getAuthProvider())) + throw new IllegalArgumentException(request.getAuthProvider()+" auth provider not found"); + + AuthenticationResponse response = new AuthenticationResponse(); + + // Before we register, we call any PreRegisterHandlers + if (preRegisterHandlers != null && !preRegisterHandlers.isEmpty()) { + for(IPreRegisterHandler preRegisterHandler : preRegisterHandlers) { + try { + preRegisterHandler.run(request); + } catch(AuthException ae) { + // Request should be cancelled. + response.setStatusCode(BasicAuthCode.FAILURE.getCode()); + response.setMessage(ae.getDetail() + ": " + ae.getMessage()); + return response; + } + } + } + + IAuthProvider provider = authProviderRegistry.getProvider(request.getAuthProvider()); + + AuthProviderResponse apResponse = provider.register(request.getCredential()); + ServiceUser serviceUser = apResponse.serviceUser; + response.setStatusCode(apResponse.authCode.getCode()); + response.setMessage(apResponse.authCode.getMessage()); + + if(serviceUser != null) { + // Registration successful + logger.debug(provider.getID() + " Registration success"); + serviceUser.setAuthProviderID(provider.getID()); // Set the provider ID just in case the provider didn't + } + else { + // Registration failed + logger.warn("REGISTRATION FAILED: (" + provider.getID() + "): Unable to retrieve valid Service User"); + logger.warn(" ERROR: ("+response.getStatusCode()+") "+response.getMessage()); + } + + // Before we send back the response, we call any PostRegisterHandlers + if (postRegisterHandlers != null && !postRegisterHandlers.isEmpty()) { + for(IPostRegisterHandler postRegisterHandler : postRegisterHandlers) { + try { + response = postRegisterHandler.run(request, response, serviceUser); + } catch(AuthException ae) { + // Since we are "post" register, we are not going to undo the register. + } + } + } + + return response; + } + public AuthenticationResponse authenticate(AuthenticationRequest request) throws IllegalArgumentException{ if(!authProviderRegistry.hasProvider(request.getAuthProvider())) throw new IllegalArgumentException(request.getAuthProvider()+" auth provider not found"); AuthenticationResponse response = new AuthenticationResponse(); + // Before we authenticate, we call any PreAuthenticateHandlers + if (preAuthenticateHandlers != null && !preAuthenticateHandlers.isEmpty()) { + for(IPreAuthenticateHandler preAuthenticateHandler : preAuthenticateHandlers) { + try { + preAuthenticateHandler.run(request); + } catch(AuthException ae) { + // Authenticate should be cancelled. + response.setStatusCode(BasicAuthCode.FAILURE.getCode()); + return response; + } + } + } + IAuthProvider provider = authProviderRegistry.getProvider(request.getAuthProvider()); AuthProviderResponse apResponse = provider.authenticate(request.getCredential()); @@ -71,6 +224,17 @@ public AuthenticationResponse authenticate(AuthenticationRequest request) throws logger.warn("LOGIN FAILED: (" + provider.getID() + "): Unable to retrieve valid Service User"); logger.warn(" ERROR: ("+response.getStatusCode()+") "+response.getMessage()); } + + // Before we send back the authenticate, we call any PostAuthenticateHandlers + if (postAuthenticateHandlers != null && !postAuthenticateHandlers.isEmpty()) { + for(IPostAuthenticateHandler postAuthenticateHandler : postAuthenticateHandlers) { + try { + response = postAuthenticateHandler.run(request, response, serviceUser); + } catch(AuthException ae) { + // Since we are "post" authenticate, we are not going to undo the authenticate. + } + } + } return response; } @@ -138,7 +302,6 @@ else if (listUserAccounts.size() > 1) { * @param serviceUser * @return */ - @SuppressWarnings("unchecked") private User findUser(ServiceUser serviceUser) throws SyncDataException { Session s = sessionFactoryAuth.openSession(); @@ -146,22 +309,41 @@ private User findUser(ServiceUser serviceUser) throws SyncDataException { // Attempt to find this user by finding a matching UserIdentifier. if (serviceUser.getIdentifiers() != null && serviceUser.getIdentifiers().size() > 0) { - String strFindUserIdentifier = "SELECT ui.user FROM UserIdentifier ui WHERE"; + String strFindUserIdentifier = "SELECT u.ID, u.dateCreated, u.dateModified, u.firstName, u.lastName FROM UserIdentifier ui, User u WHERE"; int counter = 0; for(ServiceIdentifier nextServiceIdentifier : serviceUser.getIdentifiers()) { - if (counter > 0) + if (counter > 0) { strFindUserIdentifier += " OR "; + } + else { + strFindUserIdentifier += "("; + } strFindUserIdentifier += " ui.type='" + nextServiceIdentifier.getParadigm() + "' AND ui.userIdentifier='" + nextServiceIdentifier.getValue() + "'"; counter++; } - - Query q = s.createQuery(strFindUserIdentifier); - List userList = (List) q.list(); - if (userList.size() == 1) { - theUser = userList.get(0); + if (counter > 0) { + strFindUserIdentifier += ") "; + } + strFindUserIdentifier += " AND u.ID=ui.user_ID GROUP BY u.ID"; + + Query q = s.createSQLQuery(strFindUserIdentifier); + List queryResult = null; + try { + queryResult = q.list(); + } catch(Exception e) { + throw new SyncDataException(e); + } + if (queryResult.size() == 1) { + Object[] firstResult = (Object[]) queryResult.get(0); + theUser = new User(); + theUser.setID((String) firstResult[0]); + theUser.setDateCreated(firstResult[1] != null ? (Date) firstResult[1] : null); + theUser.setDateModified(firstResult[2] != null ? (Date) firstResult[2] : null); + theUser.setFirstName(firstResult[3] != null ? (String) firstResult[3] : null); + theUser.setLastName(firstResult[4] != null ? (String) firstResult[4] : null); } - else if (userList.size() > 1) { - throw new SyncDataException(userList.size() + " UserIdentifiers found for serviceUser " + serviceUser.getId(), 1001); + else if (queryResult.size() > 1) { + throw new SyncDataException(queryResult.size() + " UserIdentifiers found for serviceUser " + serviceUser.getId(), 1001); } } @@ -202,6 +384,8 @@ public UserAccount getOrCreateUserAccount(ServiceUser serviceUser, IAuthProvider theFoundUserAccount = new UserAccount(); theFoundUserAccount.setAuthProviderID(serviceUser.getAuthProviderID()); + theFoundUserAccount.setAccessToken(serviceUser.getAccessToken()); + theFoundUserAccount.setRefreshToken(serviceUser.getRefreshToken()); theFoundUserAccount.setUser(theUser); theFoundUserAccount.setDateCreated(currentDate); theFoundUserAccount.setDateModified(currentDate); @@ -257,7 +441,7 @@ public UserAccount getOrCreateUserAccount(ServiceUser serviceUser, IAuthProvider } @SuppressWarnings("rawtypes") - private UserToken loginUserAccount(UserAccount theUserAccount, String clientId, String deviceId) { + protected UserToken loginUserAccount(UserAccount theUserAccount, String clientId, String deviceId) { Session s = null; UserToken theUserToken = null; try { @@ -361,7 +545,7 @@ public void ensureAnchorUserExists(ServiceUser serviceUser, User user){ private EntityImplementation getUserAnchorEntityImplementation(){ ManifestHelper.setManifest(manifest); EntityImplementation userAnchorEI = null; - List userAnchorMappedClasses = MappedClass.findEntityImplementation(IUserAnchor.class); + List userAnchorMappedClasses = MappedClass.findRootEntityImplementation(IUserAnchor.class); if (userAnchorMappedClasses.size() > 0) { userAnchorEI = userAnchorMappedClasses.get(0); } @@ -375,7 +559,7 @@ private EntityImplementation getUserAnchorEntityImplementation(){ private EntityImplementation getUserRoleEntityImplementation(){ ManifestHelper.setManifest(manifest); EntityImplementation userRoleEI = null; - List userRoleMappedClasses = MappedClass.findEntityImplementation(IUserRole.class); + List userRoleMappedClasses = MappedClass.findRootEntityImplementation(IUserRole.class); if (userRoleMappedClasses.size() > 0) { userRoleEI = userRoleMappedClasses.get(0); } @@ -438,7 +622,7 @@ protected IUserAnchor addOrUpdateUserAnchorFromServiceUserList(ServiceUser servi try { EntityImplementation eiUserAnchor = getUserAnchorEntityImplementation(); - List userIdentifierEntityImplementations = MappedClass.findEntityImplementation(IUserIdentifier.class); + List userIdentifierEntityImplementations = MappedClass.findRootEntityImplementation(IUserIdentifier.class); if (userIdentifierEntityImplementations.size() > 0) { List identifiersToSave = new ArrayList(); @@ -599,7 +783,7 @@ else if (serviceUser.getRoleNames().contains(nextUserRole.getRoleName())) { if (!isInaccurateList && !serviceUserRoleExists) { logger.warn("Deleting role " + nextUserRole.getRoleName() + " for " + user.getID()); - syncAgentService.systemDeleteObject(nextUserRole, null, true, new HashSet()); + syncAgentService.systemDeleteObject(BaseDataObject.toClassIdPair(nextUserRole), null, true); } else updatedUserRoles.add(nextUserRole); } diff --git a/src/main/java/com/percero/agents/auth/services/BasicAuthCode.java b/src/main/java/com/percero/agents/auth/services/BasicAuthCode.java new file mode 100644 index 0000000..912014e --- /dev/null +++ b/src/main/java/com/percero/agents/auth/services/BasicAuthCode.java @@ -0,0 +1,13 @@ +package com.percero.agents.auth.services; + +import com.percero.agents.auth.vo.AuthCode; + +public class BasicAuthCode extends AuthCode { + + public static final BasicAuthCode BAD_USER_PASS = new BasicAuthCode(433, "Bad username or password"); + + private BasicAuthCode(int code, String message){ + super(code, message); + } + +} diff --git a/src/main/java/com/percero/agents/auth/services/BasicAuthProvider.java b/src/main/java/com/percero/agents/auth/services/BasicAuthProvider.java new file mode 100644 index 0000000..e9fe78b --- /dev/null +++ b/src/main/java/com/percero/agents/auth/services/BasicAuthProvider.java @@ -0,0 +1,140 @@ +package com.percero.agents.auth.services; + +import org.apache.log4j.Logger; +import org.springframework.util.StringUtils; + +import com.percero.agents.auth.vo.AuthCode; +import com.percero.agents.auth.vo.AuthProviderResponse; +import com.percero.agents.auth.vo.BasicAuthCredential; +import com.percero.agents.auth.vo.ServiceUser; + +/** + * Basic AuthProvider implementation for database username/password + * authentication. + * + * @author Collin Brown + */ +public class BasicAuthProvider implements IAuthProvider { + + private static Logger logger = Logger.getLogger(BasicAuthProvider.class); + + public static final String ID = "activestack:basic"; // The unique ID of this auth provider. + + public String getID() { + return ID; + } + + private DatabaseHelper authDatabaseHelper = null; + + + /** + * @param jsonCredentialString - JSON String in `{"username":, "password":, "metadata":{}}` format + * @return + */ + public AuthProviderResponse authenticate(String jsonCredentialString) { + AuthProviderResponse response = new AuthProviderResponse(); + ServiceUser serviceUser = null; + BasicAuthCredential cred = null; + // Attempt to de-serialize the credential as JSON first, then as ":" delimited string. + try { + cred = BasicAuthCredential.fromJsonString(jsonCredentialString); + } catch(Exception e) { + // Do nothing. + } + if (cred == null) { + cred = BasicAuthCredential.fromString(jsonCredentialString); + } + + // If cred is empty, it means that we didn't find any valid/parseable credentials. + if (!StringUtils.hasText(cred.getUsername())) { + response.authCode = BasicAuthCode.BAD_USER_PASS; + logger.debug("AUTH FAILURE unable to parse credentials: " + response.authCode.getMessage()); + return response; + } + + logger.debug("Autheticating user " + cred.getUsername()); + + serviceUser = authDatabaseHelper.getServiceUser(cred); + + if (serviceUser == null) { + response.authCode = BasicAuthCode.BAD_USER_PASS; + logger.debug("AUTH FAILURE for user " + cred.getUsername() + ": " + response.authCode.getMessage()); + } + else { + serviceUser.setAuthProviderID(getID()); + response.serviceUser = serviceUser; + response.authCode = AuthCode.SUCCESS; + logger.debug("AUTH SUCCESS for user " + cred.getUsername()); + } + + return response; + } + + /** + * @param jsonRegistrationString - JSON String in `{"username":, "password":, "metadata":{}}` format + * @return + */ + public AuthProviderResponse register(String jsonRegistrationString) { + AuthProviderResponse response = new AuthProviderResponse(); + ServiceUser serviceUser = null; + BasicAuthCredential cred = null; + // Attempt to de-serialize the credential as JSON first, then as ":" delimited string. + try { + cred = BasicAuthCredential.fromJsonString(jsonRegistrationString); + } catch(Exception e) { + // Do nothing. + } + if (cred == null) { + cred = BasicAuthCredential.fromString(jsonRegistrationString); + } + + // If cred is empty, it means that we didn't find any valid/parseable credentials. + if (!StringUtils.hasText(cred.getUsername())) { + response.authCode = BasicAuthCode.BAD_USER_PASS; + logger.debug("REGISTER FAILURE unable to parse credentials: " + response.authCode.getMessage()); + return response; + } + + logger.debug("Registering user " + cred.getUsername()); + + try { + serviceUser = authDatabaseHelper.registerUser(cred, getID()); + + if (serviceUser == null) { + response.authCode = BasicAuthCode.FAILURE; + logger.debug("REGISTER FAILURE for user " + cred.getUsername() + ": " + response.authCode.getMessage()); + } + else { + serviceUser.setAuthProviderID(getID()); + response.serviceUser = serviceUser; + response.authCode = AuthCode.SUCCESS; + logger.debug("REGISTER SUCCESS for user " + cred.getUsername()); + } + } catch (AuthException e) { + logger.error(e); + // Based on the type of exception, return a specific result. + if (AuthException.DUPLICATE_USER_NAME.equalsIgnoreCase(e.getDetail())) { + response.authCode = BasicAuthCode.DUPLICATE_USER_NAME; + } + else if (AuthException.DATA_ERROR.equalsIgnoreCase(e.getDetail())) { + response.authCode = BasicAuthCode.FAILURE; + } + else if (AuthException.INVALID_USER_IDENTIFIER.equalsIgnoreCase(e.getDetail())) { + response.authCode = BasicAuthCode.FAILURE; + } + else if (AuthException.INVALID_USER_PASSWORD.equalsIgnoreCase(e.getDetail())) { + response.authCode = BasicAuthCode.BAD_USER_PASS; + } + } catch(Exception e) { + // Handle any other type of Exception here. + logger.error(e); + response.authCode = BasicAuthCode.FAILURE; + } + + return response; + } + + public BasicAuthProvider(DatabaseHelper authDatabaseHelper) { + this.authDatabaseHelper = authDatabaseHelper; + } +} diff --git a/src/main/java/com/percero/agents/auth/services/BasicAuthProviderFactory.java b/src/main/java/com/percero/agents/auth/services/BasicAuthProviderFactory.java new file mode 100644 index 0000000..4e2a72c --- /dev/null +++ b/src/main/java/com/percero/agents/auth/services/BasicAuthProviderFactory.java @@ -0,0 +1,42 @@ +package com.percero.agents.auth.services; + +import javax.annotation.PostConstruct; + +import org.apache.log4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + * Props up a BasicAuthProvider, which provides basic username/password + * authentication against the auth database. + * + * @author Collin Brown + * + */ +@Component +public class BasicAuthProviderFactory { + + private static Logger logger = Logger.getLogger(BasicAuthProviderFactory.class); + + @Autowired + AuthProviderRegistry authProviderRegistry; + + @Autowired + DatabaseHelper authDatabaseHelper; + + @Autowired @Value("$pf{auth.basic.enabled:false}") + Boolean basicAuthEnabled = false; + + @PostConstruct + public void init(){ + if (basicAuthEnabled != null && basicAuthEnabled.booleanValue()) { + BasicAuthProvider provider = new BasicAuthProvider(authDatabaseHelper); + logger.info("BasicAuth:.............ENABLED"); + authProviderRegistry.addProvider(provider); + } + else { + logger.info("BasicAuth:............DISABLED"); + } + } +} diff --git a/src/main/java/com/percero/agents/auth/services/DatabaseHelper.java b/src/main/java/com/percero/agents/auth/services/DatabaseHelper.java index 0432696..de758a3 100644 --- a/src/main/java/com/percero/agents/auth/services/DatabaseHelper.java +++ b/src/main/java/com/percero/agents/auth/services/DatabaseHelper.java @@ -1,6 +1,7 @@ package com.percero.agents.auth.services; import java.util.ArrayList; +import java.util.Date; import java.util.List; import java.util.UUID; @@ -8,14 +9,17 @@ import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.SessionFactory; +import org.hibernate.Transaction; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import com.google.gson.JsonObject; import com.percero.agents.auth.hibernate.AuthHibernateUtils; -import com.percero.agents.auth.vo.AuthProvider; +import com.percero.agents.auth.vo.BasicAuthCredential; +import com.percero.agents.auth.vo.ServiceIdentifier; import com.percero.agents.auth.vo.ServiceUser; +import com.percero.agents.auth.vo.User; import com.percero.agents.auth.vo.UserIdentifier; import com.percero.agents.auth.vo.UserPassword; @@ -34,13 +38,20 @@ public List getUserRoleNames(String login, String consumerKey, String co return result; } + public ServiceUser getServiceUser(BasicAuthCredential credential) { + if (credential == null) { + return null; + } + return getServiceUser(credential.getUsername(), credential.getPassword()); + } + @SuppressWarnings("unchecked") public ServiceUser getServiceUser(String userName, String password) { ServiceUser result = null; Session s = null; try { s = sessionFactoryAuth.openSession(); - Query q = s.createQuery("FROM UserPassword up WHERE up.userIdentifier.userIdentifier=:userName AND up.password=:password"); + Query q = s.createQuery("FROM UserPassword up WHERE up.userIdentifier.userIdentifier=:userName AND up.password=PASSWORD(:password)"); q.setString("userName", userName); q.setString("password", password); UserPassword userPassword = (UserPassword) AuthHibernateUtils.cleanObject(q.uniqueResult()); @@ -51,12 +62,39 @@ public ServiceUser getServiceUser(String userName, String password) { if (userIdentifier != null) { result = new ServiceUser(); result.setId(userIdentifier.getUserIdentifier()); - if (StringUtils.hasText(userIdentifier.getUserIdentifier())) { - result.setEmails(new ArrayList()); - result.getEmails().add(userIdentifier.getUserIdentifier()); + result.setIdentifiers(new ArrayList()); + + ServiceIdentifier userServiceIdentifier = new ServiceIdentifier(ServiceIdentifier.ACTIVESTACK_USERID, userIdentifier.getUser().getID()); + result.getIdentifiers().add(userServiceIdentifier); + + // Now retrieve all the EMAIL types for this User (if any exist). + q = s.createQuery("FROM UserIdentifier ui WHERE ui.user.ID=:userId"); + q.setString("userId", userIdentifier.getUser().getID()); + List userIdentifiers = (List) AuthHibernateUtils.cleanObject(q.list()); + result.setEmails(new ArrayList()); + + if (userIdentifiers != null && !userIdentifiers.isEmpty()) { + for(UserIdentifier nextIdentifier : userIdentifiers) { + if ("email".equalsIgnoreCase(nextIdentifier.getType())) { + result.getEmails().add(nextIdentifier.getUserIdentifier()); + } + + ServiceIdentifier serviceIdentifier = new ServiceIdentifier(nextIdentifier.getType(), nextIdentifier.getUserIdentifier()); + result.getIdentifiers().add(serviceIdentifier); + } + } + + // Now get the User details. + q = s.createQuery("FROM User u WHERE u.ID=:userId"); + q.setString("userId", userIdentifier.getUser().getID()); + User user = (User) AuthHibernateUtils.cleanObject(q.uniqueResult()); + if (user != null) { + result.setFirstName(user.getFirstName()); + result.setLastName(user.getLastName()); } + result.setAccessToken(UUID.randomUUID().toString()); - result.setAuthProviderID(AuthProvider.DATABASE.toString()); +// result.setAuthProviderID(AuthProvider.DATABASE.toString()); result.setAreRoleNamesAccurate(false); } @@ -70,6 +108,117 @@ public ServiceUser getServiceUser(String userName, String password) { return result; } + public ServiceUser registerUser(BasicAuthCredential credential, String paradigm) throws AuthException { + // If included in the metadata, we want to pull in the first and last name. + String firstName = credential.retrieveMetadataString(BasicAuthCredential.FIRST_NAME); + String lastName = credential.retrieveMetadataString(BasicAuthCredential.LAST_NAME); + String email = credential.retrieveMetadataString(BasicAuthCredential.EMAIL); + return registerUser(credential.getUsername(), credential.getPassword(), paradigm, firstName, lastName, email); + } + + @SuppressWarnings("unchecked") + public ServiceUser registerUser(String userName, String password, String paradigm, String firstName, + String lastName, String email) throws AuthException { + if (!StringUtils.hasText(userName) || !StringUtils.hasText(password) || !StringUtils.hasText(paradigm)) { + // Invalid input. + throw new AuthException("Missing required data", AuthException.INVALID_DATA); + } + + Session s = null; + try { + s = sessionFactoryAuth.openSession(); + + // First make sure this User does NOT already exist. + Query q = s.createQuery("FROM UserPassword up WHERE up.userIdentifier.userIdentifier=:userName"); + q.setString("userName", userName); + List userPasswords = (List) AuthHibernateUtils.cleanObject(q.list()); + + if (userPasswords != null && !userPasswords.isEmpty()) { + // User name exists, so unable to register User. + throw new AuthException("User name already exists", AuthException.DUPLICATE_USER_NAME); + } + + // If a user exists with the same email, then we fail with duplicate email. + if (StringUtils.hasText(email)) { + q = s.createQuery("FROM UserIdentifier ui WHERE ui.userIdentifier=:userName AND ui.type=:paradigm"); + q.setString("userName", email); + q.setString("paradigm", ServiceIdentifier.EMAIL); + UserIdentifier userIdentifier = (UserIdentifier) AuthHibernateUtils.cleanObject(q.uniqueResult()); + if (userIdentifier != null) { + throw new AuthException("Email already exists", AuthException.DUPLICATE_USER_NAME); + } + } + + // Now check to see if the UserIdentifier exists. + q = s.createQuery("FROM UserIdentifier ui WHERE ui.userIdentifier=:userName AND ui.type=:paradigm"); + q.setString("userName", userName); + q.setString("paradigm", paradigm); + + User user = null; + UserIdentifier userIdentifier = (UserIdentifier) AuthHibernateUtils.cleanObject(q.uniqueResult()); + if (userIdentifier == null) { + // Need to create the UserIdentifier and User. + Transaction tx = s.beginTransaction(); + user = new User(); + user.setID(UUID.randomUUID().toString()); + user.setFirstName(firstName); + user.setLastName(lastName); + user.setDateCreated(new Date()); + s.save(user); + user = (User) s.get(User.class, user.getID()); + + userIdentifier = new UserIdentifier(); + userIdentifier.setID(UUID.randomUUID().toString()); + userIdentifier.setUser(user); + userIdentifier.setType(paradigm); + userIdentifier.setUserIdentifier(userName); + + s.save(userIdentifier); + + tx.commit(); + + // Make sure we have successfully created this UserIdentifier by re-running the query. + userIdentifier = (UserIdentifier) AuthHibernateUtils.cleanObject(q.uniqueResult()); + } + + // If UserIdentifier is still null, then we have a problem. + if (userIdentifier == null) { + throw new AuthException("Invalid User Identifier", AuthException.INVALID_USER_IDENTIFIER); + } + + // If an email was included, we can also create a UserIdentifier for + // email. We know if does NOT currently exist because we checked for + // that above. + if (StringUtils.hasText(email)) { + Transaction tx = s.beginTransaction(); + UserIdentifier emailIdentifier = new UserIdentifier(); + emailIdentifier.setID(UUID.randomUUID().toString()); + emailIdentifier.setUser(user); + emailIdentifier.setType(ServiceIdentifier.EMAIL); + emailIdentifier.setUserIdentifier(email); + s.save(emailIdentifier); + tx.commit(); + } + + // Now we can create the UserPassword. + q = s.createSQLQuery("INSERT INTO UserPassword (`ID`, `password`, `userIdentifier_ID`) VALUES ('" + UUID.randomUUID().toString() + "',PASSWORD('" + password + "'),'" + userIdentifier.getID() + "')"); + int updateResult = q.executeUpdate(); + + if (updateResult == 1) { + // We have successfully registered the user name. + return getServiceUser(userName, password); + } + else { + // Something went wrong + throw new AuthException("Invalid User Password", AuthException.INVALID_USER_PASSWORD); + } + } finally { + if (s != null) { + s.close(); + } + } + } + public String getJsonObjectStringValue(JsonObject jsonObject, String fieldName) { if (jsonObject.has(fieldName)) diff --git a/src/main/java/com/percero/agents/auth/services/GoogleAuthProvider.java b/src/main/java/com/percero/agents/auth/services/GoogleAuthProvider.java index b24b45e..5d2846b 100644 --- a/src/main/java/com/percero/agents/auth/services/GoogleAuthProvider.java +++ b/src/main/java/com/percero/agents/auth/services/GoogleAuthProvider.java @@ -92,7 +92,7 @@ public String getID() { @Autowired @Value("$pf{oauth.google.admin}") private String admin; - @Autowired @Value("$pf{oauth.google.application_name}") + @Autowired @Value("$pf{oauth.google.application_name:}") private String applicationName; @Autowired @Value("$pf{oauth.google.use_role_names:true}") @@ -177,6 +177,12 @@ public void init(){ } } + public AuthProviderResponse register(String credential) { + AuthProviderResponse result = new AuthProviderResponse(); + result.authCode = AuthCode.FORBIDDEN; + return result; + } + public AuthProviderResponse authenticate(String credential) { AuthProviderResponse result = new AuthProviderResponse(); try { diff --git a/src/main/java/com/percero/agents/auth/services/GoogleHelper.java b/src/main/java/com/percero/agents/auth/services/GoogleHelper.java index 7262383..53ab48f 100644 --- a/src/main/java/com/percero/agents/auth/services/GoogleHelper.java +++ b/src/main/java/com/percero/agents/auth/services/GoogleHelper.java @@ -96,7 +96,7 @@ public class GoogleHelper implements IAuthHelper { @Autowired @Value("$pf{oauth.google.admin}") private String admin; - @Autowired @Value("$pf{oauth.google.application_name}") + @Autowired @Value("$pf{oauth.google.application_name:}") private String applicationName; @Autowired @Value("$pf{oauth.google.use_role_names:true}") @@ -251,7 +251,7 @@ public ServiceUser authenticateOAuthCode(String code, String redirectUri) throws return serviceUser; } } catch(Exception e) { - log.error("Unable to get authenticate oauth code", e); + log.error("Unable to get authenticate oauth code: " + e.getMessage()); return null; } } diff --git a/src/main/java/com/percero/agents/auth/services/IAuthProvider.java b/src/main/java/com/percero/agents/auth/services/IAuthProvider.java index ebf1b22..4ae759b 100644 --- a/src/main/java/com/percero/agents/auth/services/IAuthProvider.java +++ b/src/main/java/com/percero/agents/auth/services/IAuthProvider.java @@ -1,7 +1,6 @@ package com.percero.agents.auth.services; import com.percero.agents.auth.vo.AuthProviderResponse; -import com.percero.agents.auth.vo.ServiceUser; /** * Defines the interface for providing additional authentication behavior into the ActiveStack @@ -14,10 +13,16 @@ public interface IAuthProvider { String getID(); /** - * Authenticates a token and returns a ServiceUser that cooresponds to the credentials provided + * Authenticates a token and returns a ServiceUser that corresponds to the credentials provided * @param credential - A String to be interpreted by the provider as an authentication credential * @return ServiceUser */ AuthProviderResponse authenticate(String credential); + /** + * If supported but the AuthProvider, registers the user. + * @param credential - A String to be interpreted by the provider as an authentication credential + * @return + */ + AuthProviderResponse register(String credential); } diff --git a/src/main/java/com/percero/agents/auth/services/IAuthService.java b/src/main/java/com/percero/agents/auth/services/IAuthService.java index 00e3bc0..1bc4132 100644 --- a/src/main/java/com/percero/agents/auth/services/IAuthService.java +++ b/src/main/java/com/percero/agents/auth/services/IAuthService.java @@ -3,7 +3,6 @@ import java.util.List; import java.util.Set; -import com.percero.agents.auth.vo.AuthProvider; import com.percero.agents.auth.vo.OAuthResponse; import com.percero.agents.auth.vo.OAuthToken; import com.percero.agents.auth.vo.ServiceUser; @@ -109,8 +108,9 @@ public interface IAuthService { * * @param aUserId * @param aToken - * @param aClientId + * @param clientIds * @return TRUE if user/client successfully logged out, FALSE if user/client unable to be logged out */ - public Boolean logoutUser(String aUserId, String aToken, String aClientId); + public Boolean logoutUser(String aUserId, String aToken, Set clientIds); + public Boolean logoutUser(String aUserId, String aToken, String clientId); } diff --git a/src/main/java/com/percero/agents/auth/services/IPostAuthenticateHandler.java b/src/main/java/com/percero/agents/auth/services/IPostAuthenticateHandler.java new file mode 100644 index 0000000..de3f555 --- /dev/null +++ b/src/main/java/com/percero/agents/auth/services/IPostAuthenticateHandler.java @@ -0,0 +1,21 @@ +package com.percero.agents.auth.services; + +import com.percero.agents.auth.vo.AuthenticationRequest; +import com.percero.agents.auth.vo.AuthenticationResponse; +import com.percero.agents.auth.vo.ServiceUser; + +public interface IPostAuthenticateHandler { + + /** + * Takes in an AuthenticationResponse and is able to modify that response or + * even return a new one. Note that the returned response is the response + * sent back to the authenticating client. + * + * @param registerRequest + * @param registerResponse + * @param serviceUser + * @return + * @throws AuthException + */ + AuthenticationResponse run(AuthenticationRequest request, AuthenticationResponse registerResponse, ServiceUser serviceUser) throws AuthException; +} diff --git a/src/main/java/com/percero/agents/auth/services/IPostRegisterHandler.java b/src/main/java/com/percero/agents/auth/services/IPostRegisterHandler.java new file mode 100644 index 0000000..83c9276 --- /dev/null +++ b/src/main/java/com/percero/agents/auth/services/IPostRegisterHandler.java @@ -0,0 +1,21 @@ +package com.percero.agents.auth.services; + +import com.percero.agents.auth.vo.AuthenticationRequest; +import com.percero.agents.auth.vo.AuthenticationResponse; +import com.percero.agents.auth.vo.ServiceUser; + +public interface IPostRegisterHandler { + + /** + * Takes in an AuthenticationResponse and is able to modify that response or + * even return a new one. Note that the returned response is the response + * sent back to the registering client. + * + * @param registerRequest + * @param registerResponse + * @param serviceUser + * @return + * @throws AuthException + */ + AuthenticationResponse run(AuthenticationRequest request, AuthenticationResponse registerResponse, ServiceUser serviceUser) throws AuthException; +} diff --git a/src/main/java/com/percero/agents/auth/services/IPreAuthenticateHandler.java b/src/main/java/com/percero/agents/auth/services/IPreAuthenticateHandler.java new file mode 100644 index 0000000..41f8dcb --- /dev/null +++ b/src/main/java/com/percero/agents/auth/services/IPreAuthenticateHandler.java @@ -0,0 +1,18 @@ +package com.percero.agents.auth.services; + +import com.percero.agents.auth.vo.AuthenticationRequest; + +public interface IPreAuthenticateHandler { + + /** + * Takes in an AuthenticationResponse and to perform any necessary + * pre-authenticate checks. If an Exception is thrown, then the authenticate + * request is cancelled. + * + * @param registerRequest + * @param registerResponse + * @return + * @throws AuthException + */ + void run(AuthenticationRequest request) throws AuthException; +} diff --git a/src/main/java/com/percero/agents/auth/services/IPreRegisterHandler.java b/src/main/java/com/percero/agents/auth/services/IPreRegisterHandler.java new file mode 100644 index 0000000..83d756a --- /dev/null +++ b/src/main/java/com/percero/agents/auth/services/IPreRegisterHandler.java @@ -0,0 +1,18 @@ +package com.percero.agents.auth.services; + +import com.percero.agents.auth.vo.AuthenticationRequest; + +public interface IPreRegisterHandler { + + /** + * Takes in an AuthenticationResponse and to perform any necessary + * pre-register checks. If an Exception is thrown, then the register request + * is cancelled. + * + * @param registerRequest + * @param registerResponse + * @return + * @throws AuthException + */ + void run(AuthenticationRequest request) throws AuthException; +} diff --git a/src/main/java/com/percero/agents/auth/services/InMemoryAuthProvider.java b/src/main/java/com/percero/agents/auth/services/InMemoryAuthProvider.java index 743fe68..05e97b4 100644 --- a/src/main/java/com/percero/agents/auth/services/InMemoryAuthProvider.java +++ b/src/main/java/com/percero/agents/auth/services/InMemoryAuthProvider.java @@ -17,6 +17,34 @@ public class InMemoryAuthProvider implements IAuthProvider { public String getID() { return ID; } + + public AuthProviderResponse register(String credential) { + AuthProviderResponse result = new AuthProviderResponse(); + BasicAuthCredential cred = BasicAuthCredential.fromString(credential); + + String hashPass = DigestUtils.sha1Hex(cred.getPassword()); + + InMemoryAuthProviderUser user = users.get(cred.getUsername()); + if (user == null) { + user = new InMemoryAuthProviderUser(); + user.setEmail(cred.getUsername()); + user.setPassHash(hashPass); + users.put(cred.getUsername(), user); + } + + ServiceUser serviceUser = new ServiceUser(); + serviceUser.setAuthProviderID(getID()); + serviceUser.setId(cred.getUsername()); + serviceUser.setFirstName(user.getFirstName()); + serviceUser.setLastName(user.getLastName()); + serviceUser.getEmails().add(user.getEmail()); + serviceUser.setAreRoleNamesAccurate(true); + serviceUser.getIdentifiers().add(new ServiceIdentifier("email", user.getEmail())); + result.serviceUser = serviceUser; + result.authCode = AuthCode.SUCCESS; + + return result; + } public AuthProviderResponse authenticate(String credential) { AuthProviderResponse result = new AuthProviderResponse(); diff --git a/src/main/java/com/percero/agents/auth/vo/AuthCode.java b/src/main/java/com/percero/agents/auth/vo/AuthCode.java index 8ba3cc1..ece20f2 100644 --- a/src/main/java/com/percero/agents/auth/vo/AuthCode.java +++ b/src/main/java/com/percero/agents/auth/vo/AuthCode.java @@ -6,7 +6,9 @@ public class AuthCode { public static final AuthCode SUCCESS = new AuthCode(200, "Success"); public static final AuthCode UNAUTHORIZED = new AuthCode(401, "Unauthorized"); - public static final AuthCode FORBIDDEN = new AuthCode(401, "Forbidden"); + public static final AuthCode FORBIDDEN = new AuthCode(402, "Forbidden"); + public static final AuthCode FAILURE = new AuthCode(403, "Failure"); + public static final AuthCode DUPLICATE_USER_NAME = new AuthCode(404, "Duplicate User Name"); private int code; private String message; diff --git a/src/main/java/com/percero/agents/auth/vo/BasicAuthCredential.java b/src/main/java/com/percero/agents/auth/vo/BasicAuthCredential.java index 7e43bd3..4c886fc 100644 --- a/src/main/java/com/percero/agents/auth/vo/BasicAuthCredential.java +++ b/src/main/java/com/percero/agents/auth/vo/BasicAuthCredential.java @@ -1,10 +1,24 @@ package com.percero.agents.auth.vo; +import java.util.HashMap; +import java.util.Map; + +import org.boon.core.value.CharSequenceValue; +import org.boon.json.ObjectMapper; +import org.boon.json.implementation.ObjectMapperImpl; + +import com.google.common.base.CaseFormat; + /** * Created by jonnysamps on 8/27/15. */ public class BasicAuthCredential { - private String username; + + public static final String FIRST_NAME = "firstName"; + public static final String LAST_NAME = "lastName"; + public static final String EMAIL = "email"; + + private String username; public String getUsername(){ return username; } @@ -19,10 +33,29 @@ public String getPassword(){ public void setPassword(String val){ this.password = val; } + + private Map metadata; + public Map getMetadata() { + return metadata; + } + public void setMetadata(Map metadata) { + this.metadata = metadata; + } + public BasicAuthCredential(){ + + } + public BasicAuthCredential(String username, String password){ + this.username = username; + this.password = password; + this.metadata = new HashMap(); + } + + public BasicAuthCredential(String username, String password, Map metadata){ this.username = username; this.password = password; + this.metadata = metadata; } /** @@ -38,4 +71,65 @@ public static BasicAuthCredential fromString(String credential){ } return result; } + + /** + * Used to deserialize from `{"username":, "password":}` format to an object + * @param credential + * @return + */ + public static BasicAuthCredential fromJsonString(String jsonCredential){ + ObjectMapper om = new ObjectMapperImpl(); + BasicAuthCredential result = om.fromJson(jsonCredential, BasicAuthCredential.class); + return result; + } + + public String retrieveMetadataString(String keyName) { + return BasicAuthCredential.retrieveMetadataString(getMetadata(), keyName); + } + + public static String retrieveMetadataString(Map metadata, String keyName) { + return retrieveMetadataString(metadata, keyName, false); + } + public static String retrieveMetadataString(Map metadata, String keyName, boolean caseSensitive) { + String result = ""; + if (metadata != null && keyName != null) { + // We won't allow for non-casesensitive lookups on large datasets. + if (caseSensitive || metadata.size() > 1000) { + Object value = metadata.get(keyName); + if (value != null) { + if (value instanceof String) { + result = (String) value; + } + else if (value instanceof CharSequenceValue) { + CharSequenceValue charSequenceValue = (CharSequenceValue) value; + result = charSequenceValue.stringValue(); + } + } + } + else { + // Sort of defeats the purpose of a Map, but we want to allow + // flexibility in this case, so we iterate through the entry set + // of the Map to see if we can find a non-casesensitive match. + String keyLowerUnderscore = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, keyName); + for(Map.Entry nextEntry : metadata.entrySet()) { + String nextKey = nextEntry.getKey(); + if (keyName.equalsIgnoreCase(nextKey) || keyLowerUnderscore.equalsIgnoreCase(nextKey)) { + // We have found our match. + Object value = nextEntry.getValue(); + if (value != null) { + if (value instanceof String) { + result = (String) value; + } + else if (value instanceof CharSequenceValue) { + CharSequenceValue charSequenceValue = (CharSequenceValue) value; + result = charSequenceValue.stringValue(); + } + } + } + } + } + } + + return result; + } } diff --git a/src/main/java/com/percero/agents/auth/vo/DisconnectRequest.java b/src/main/java/com/percero/agents/auth/vo/DisconnectRequest.java index c52f021..c8abf03 100644 --- a/src/main/java/com/percero/agents/auth/vo/DisconnectRequest.java +++ b/src/main/java/com/percero/agents/auth/vo/DisconnectRequest.java @@ -1,5 +1,22 @@ package com.percero.agents.auth.vo; +import java.util.Set; + public class DisconnectRequest extends AuthRequest { + private String existingClientId; + public String getExistingClientId() { + return existingClientId; + } + public void setExistingClientId(String existingClientId) { + this.existingClientId = existingClientId; + } + + private Set existingClientIds; + public Set getExistingClientIds() { + return existingClientIds; + } + public void setExistingClientIds(Set existingClientIds) { + this.existingClientIds = existingClientIds; + } } diff --git a/src/main/java/com/percero/agents/auth/vo/ServiceIdentifier.java b/src/main/java/com/percero/agents/auth/vo/ServiceIdentifier.java index 0e5e0d2..84525cd 100644 --- a/src/main/java/com/percero/agents/auth/vo/ServiceIdentifier.java +++ b/src/main/java/com/percero/agents/auth/vo/ServiceIdentifier.java @@ -8,6 +8,7 @@ public class ServiceIdentifier implements Serializable { + public static final String ACTIVESTACK_USERID = "activestack:userid"; public static final String EMAIL = "email"; public static final String GITHUB = "github"; public static final String GOOGLE = "google"; diff --git a/src/main/java/com/percero/agents/auth/vo/_Super_User.java b/src/main/java/com/percero/agents/auth/vo/_Super_User.java index 459f6fd..fc3265b 100644 --- a/src/main/java/com/percero/agents/auth/vo/_Super_User.java +++ b/src/main/java/com/percero/agents/auth/vo/_Super_User.java @@ -29,6 +29,8 @@ public class _Super_User implements Serializable, IAuthCachedObject * Properties */ private String _ID; + private String _firstName; + private String _lastName; private Date _dateCreated; private Date _dateModified; private List _userAccounts; @@ -50,6 +52,29 @@ public void setID(String value) _ID = value; } + @Column + public String getFirstName() + { + return _firstName; + } + + public void setFirstName(String value) + { + _firstName = value; + } + + @Column + public String getLastName() + { + return _lastName; + } + + public void setLastName(String value) + { + _lastName = value; + } + + @Temporal(TemporalType.TIMESTAMP) @Column diff --git a/src/main/java/com/percero/agents/sync/access/RedisAccessManager.java b/src/main/java/com/percero/agents/sync/access/RedisAccessManager.java index 26989b8..0b59d2c 100644 --- a/src/main/java/com/percero/agents/sync/access/RedisAccessManager.java +++ b/src/main/java/com/percero/agents/sync/access/RedisAccessManager.java @@ -14,13 +14,11 @@ import java.util.TreeMap; import java.util.concurrent.TimeUnit; -import com.percero.framework.vo.IPerceroObject; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.core.task.TaskExecutor; import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; @@ -34,8 +32,8 @@ import com.percero.agents.sync.services.IPushSyncHelper; import com.percero.agents.sync.vo.ClassIDPair; import com.percero.agents.sync.vo.Client; +import com.percero.framework.vo.IPerceroObject; -@Component public class RedisAccessManager implements IAccessManager { private static Logger log = Logger.getLogger(RedisAccessManager.class); @@ -285,7 +283,7 @@ public Boolean upgradeClient(String clientId, String deviceId, String deviceType // Make sure this is a valid client. Boolean isValidClient = findClientByClientIdUserId(clientId, userId); if (!isValidClient) - throw new ClientException(ClientException.INVALID_CLIENT, ClientException.INVALID_CLIENT_CODE); + throw new ClientException(ClientException.INVALID_CLIENT, ClientException.INVALID_CLIENT_CODE, "Unable to upgradeClient", clientId); Set previousClientIds = null; if (deviceId != null && deviceId.length() > 0) { diff --git a/src/main/java/com/percero/agents/sync/aspects/ValidClientAspect.java b/src/main/java/com/percero/agents/sync/aspects/ValidClientAspect.java index a8a496e..18b92fe 100644 --- a/src/main/java/com/percero/agents/sync/aspects/ValidClientAspect.java +++ b/src/main/java/com/percero/agents/sync/aspects/ValidClientAspect.java @@ -21,6 +21,6 @@ public void assertValidClient(String clientId) throws Exception { //String clientId = ""; Boolean isValidClient = accessManager.validateClientByClientId(clientId); if (!isValidClient) - throw new ClientException(ClientException.INVALID_CLIENT, ClientException.INVALID_CLIENT_CODE); + throw new ClientException(ClientException.INVALID_CLIENT, ClientException.INVALID_CLIENT_CODE, "", clientId); } } diff --git a/src/main/java/com/percero/agents/sync/dao/DAORegistry.java b/src/main/java/com/percero/agents/sync/dao/DAORegistry.java index c1401ee..3c63088 100644 --- a/src/main/java/com/percero/agents/sync/dao/DAORegistry.java +++ b/src/main/java/com/percero/agents/sync/dao/DAORegistry.java @@ -24,6 +24,10 @@ public DAORegistry() { @SuppressWarnings({ "unchecked" }) private Map> dataAccessObjects = Collections.synchronizedMap(new HashMap>()); + public Map> getDataAccessObjects() { + return dataAccessObjects; + } + public void registerDataAccessObject(String name, IDataAccessObject dataAccessObject) { dataAccessObjects.put(name, dataAccessObject); } diff --git a/src/main/java/com/percero/agents/sync/exceptions/ClientException.java b/src/main/java/com/percero/agents/sync/exceptions/ClientException.java index d79246d..6dfe459 100644 --- a/src/main/java/com/percero/agents/sync/exceptions/ClientException.java +++ b/src/main/java/com/percero/agents/sync/exceptions/ClientException.java @@ -13,16 +13,23 @@ public ClientException(String name, Integer code, String desc, Throwable t) { super(name, code, desc, t); } - public ClientException(String name, Integer code, String desc) { + public ClientException(String name, Integer code, String desc, String clientId) { super(name, code, desc); - } - - public ClientException(String name, Integer code) { - super(name, code); + this.clientId = clientId; } public ClientException() { super(); } + + private String clientId; + + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } } diff --git a/src/main/java/com/percero/agents/sync/exceptions/SyncDataException.java b/src/main/java/com/percero/agents/sync/exceptions/SyncDataException.java index cefef5e..e1cca81 100644 --- a/src/main/java/com/percero/agents/sync/exceptions/SyncDataException.java +++ b/src/main/java/com/percero/agents/sync/exceptions/SyncDataException.java @@ -31,6 +31,10 @@ public SyncDataException(String name, Integer code, String desc) { super(name, code, desc); } + public SyncDataException(String name, Integer code, Throwable t) { + super(name, code, t); + } + public SyncDataException(String name, Integer code) { super(name, code); } diff --git a/src/main/java/com/percero/agents/sync/exceptions/SyncException.java b/src/main/java/com/percero/agents/sync/exceptions/SyncException.java index 4501f33..04fcf9c 100644 --- a/src/main/java/com/percero/agents/sync/exceptions/SyncException.java +++ b/src/main/java/com/percero/agents/sync/exceptions/SyncException.java @@ -21,6 +21,7 @@ public class SyncException extends Exception { public static final String METHOD_NOT_IMPLEMENTED = "methodNotImplemented"; public static final Integer METHOD_NOT_IMPLEMENTED_CODE = -106; + public static final Integer PROCESS_ERROR_CODE = -1000; public SyncException(String name, Integer code, String desc, Throwable t) { super(desc, t); @@ -40,6 +41,12 @@ public SyncException(String name, Integer code) { this.setCode(code); } + public SyncException(String name, Integer code, Throwable t) { + super(t); + this.setName(name); + this.setCode(code); + } + public SyncException(Throwable t) { super(t); } diff --git a/src/main/java/com/percero/agents/sync/helpers/PostCreateHelper.java b/src/main/java/com/percero/agents/sync/helpers/PostCreateHelper.java index 5f72b5c..8946866 100644 --- a/src/main/java/com/percero/agents/sync/helpers/PostCreateHelper.java +++ b/src/main/java/com/percero/agents/sync/helpers/PostCreateHelper.java @@ -1,5 +1,6 @@ package com.percero.agents.sync.helpers; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; @@ -81,19 +82,31 @@ public void postCreateObject(IPerceroObject perceroObject, String pusherUserId, if (pusherUserId != null && pusherUserId.length() > 0) accessManager.saveAccessJournal(pair, pusherUserId, pusherClientId); - // Sending an empty List as the ChangedFields parameter will cause the PostPutHelper to NOT check access - // managers for each field since it is impossible for change watchers to already exist for this new - // object. - postPutHelper.postPutObject(pair, pusherUserId, pusherClientId, pushToUser, new HashMap>()); - // For each association, update push the associated objects. IMappedClassManager mcm = MappedClassManagerFactory.getMappedClassManager(); MappedClass mappedClass = mcm.getMappedClassByClassName(pair.getClassName()); + // Sending an empty List as the ChangedFields parameter will cause the PostPutHelper to NOT check access + // managers for each field since it is impossible for change watchers to already exist for this new + // object. + + // Since this object has been deleted, ALL fields have changed. + Iterator itrChangedFields = mappedClass.externalizableFields.iterator(); + Map> changedFields = new HashMap>(1); + Collection changedMappedFields = new ArrayList(mappedClass.externalizableFields.size()); + while (itrChangedFields.hasNext()) { + MappedField nextChangedField = itrChangedFields.next(); + changedMappedFields.add(nextChangedField); + } + + postPutHelper.postPutObject(pair, pusherUserId, pusherClientId, pushToUser, changedFields); + Map> changedObjects = new HashMap>(); - for(MappedFieldPerceroObject nextMappedField : mappedClass.externalizablePerceroObjectFields) { + // The only relationship fields that could have changed are source relationship fields + for(MappedFieldPerceroObject nextMappedField : mappedClass.getSourceMappedFields()) { try { + // We only care about two-way relationships. if (nextMappedField.getReverseMappedField() != null) { IPerceroObject fieldValue = (IPerceroObject) nextMappedField.getValue(perceroObject); if (fieldValue != null) { diff --git a/src/main/java/com/percero/agents/sync/helpers/PostDeleteHelper.java b/src/main/java/com/percero/agents/sync/helpers/PostDeleteHelper.java index 6915fcc..030f813 100644 --- a/src/main/java/com/percero/agents/sync/helpers/PostDeleteHelper.java +++ b/src/main/java/com/percero/agents/sync/helpers/PostDeleteHelper.java @@ -1,6 +1,10 @@ package com.percero.agents.sync.helpers; import com.percero.agents.sync.access.IAccessManager; +import com.percero.agents.sync.metadata.IMappedClassManager; +import com.percero.agents.sync.metadata.MappedClass; +import com.percero.agents.sync.metadata.MappedClassManagerFactory; +import com.percero.agents.sync.metadata.MappedField; import com.percero.agents.sync.services.IPushSyncHelper; import com.percero.agents.sync.services.ISyncAgentService; import com.percero.agents.sync.vo.ClassIDPair; @@ -14,7 +18,11 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; @Component public class PostDeleteHelper { @@ -62,10 +70,7 @@ public void postDeleteObject(IPerceroObject theObject, String pusherUserId, Stri log.debug("PostDeleteHelper for " + theObject.toString() + " from clientId " + (pusherClientId == null ? "NULL" : pusherClientId)); ClassIDPair pair = new ClassIDPair(theObject.getID(), theObject.getClass().getCanonicalName()); - postDeleteObject(pair, pusherUserId, pusherClientId, pushToUser); - } - public void postDeleteObject(ClassIDPair pair, String pusherUserId, String pusherClientId, boolean pushToUser) throws Exception { // Remove all UpdateJournals for the objects. accessManager.removeUpdateJournalsByObject(pair); @@ -88,6 +93,32 @@ public void postDeleteObject(ClassIDPair pair, String pusherUserId, String pushe /*Collection deleteJournals = */accessManager.saveDeleteJournalClients(pair, clientIds, guaranteeUpdateDelivery, pusherClientId, pushToUser); pushObjectDeleteJournals(clientIds, pair.getClassName(), pair.getID()); + + IMappedClassManager mcm = MappedClassManagerFactory.getMappedClassManager(); + MappedClass mappedClass = mcm.getMappedClassByClassName(pair.getClassName()); + if (mappedClass != null) { + // Since this object has been deleted, ALL fields have changed. + Iterator itrChangedFields = mappedClass.externalizableFields.iterator(); + String[] fieldNames = new String[mappedClass.externalizableFields.size()]; + int i = 0; + while (itrChangedFields.hasNext()) { + MappedField nextChangedField = itrChangedFields.next(); + fieldNames[i] = nextChangedField.getField().getName(); + i++; + } + pushSyncHelper.enqueueCheckChangeWatcher(pair, fieldNames, null, theObject); + + Map objectsToUpdate = mappedClass.getRelatedClassIdPairMappedFieldMap(theObject, false); + Iterator> itrObjectsToUpdate = objectsToUpdate.entrySet().iterator(); + while (itrObjectsToUpdate.hasNext()) { + Entry nextObjectToUpdate = itrObjectsToUpdate.next(); + Map> changedFields = new HashMap>(); + Collection changedMappedFields = new ArrayList(1); + changedMappedFields.add(nextObjectToUpdate.getValue()); + changedFields.put(nextObjectToUpdate.getKey(), changedMappedFields); + postPutHelper.postPutObject(nextObjectToUpdate.getKey(), pusherUserId, pusherClientId, true, changedFields); + } + } } protected void pushObjectDeleteJournals(Collection clientIds, String className, String classId) { diff --git a/src/main/java/com/percero/agents/sync/helpers/PostPutHelper.java b/src/main/java/com/percero/agents/sync/helpers/PostPutHelper.java index 41607ea..538322b 100644 --- a/src/main/java/com/percero/agents/sync/helpers/PostPutHelper.java +++ b/src/main/java/com/percero/agents/sync/helpers/PostPutHelper.java @@ -80,21 +80,12 @@ public void postPutObject(ClassIDPair pair, String pusherUserId, String pusherCl break; } } -// Iterator itrChangedFieldsKeys = changedFields.keySet().iterator(); -// while (itrChangedFieldsKeys.hasNext()) { -// ClassIDPair nextPair = itrChangedFieldsKeys.next(); -// if (nextPair.equals(pair)) { -// pairChangedFields = changedFields.get(nextPair); -// break; -// } -// } } pushObjectUpdateJournals(clientIds, pair, pairChangedFields); // Now run past the ChangeWatcher. if (changedFields == null || changedFields.isEmpty()) { - enqueueCheckChangeWatcher(pair, null, null, oldValue); -// accessManager.checkChangeWatchers(pair, null, null); + pushSyncHelper.enqueueCheckChangeWatcher(pair, null, null, oldValue); } else { // TODO: Need to somehow aggregate changes per client/object. @@ -127,42 +118,12 @@ public void postPutObject(ClassIDPair pair, String pusherUserId, String pusherCl } // Swap out inline processing for a worker queue - enqueueCheckChangeWatcher(thePair, fieldNames, null, oldValue); -// accessManager.checkChangeWatchers(thePair, fieldNames, null); + pushSyncHelper.enqueueCheckChangeWatcher(thePair, fieldNames, null, oldValue); } } - -// Iterator itrChangedFieldKeyset = changedFields.keySet().iterator(); -// while (itrChangedFieldKeyset.hasNext()) { -// ClassIDPair thePair = itrChangedFieldKeyset.next(); -// Collection changedMappedFields = changedFields.get(thePair); -// Iterator itrChangedFields = changedMappedFields.iterator(); -// String[] fieldNames = new String[changedMappedFields.size()]; -// int i = 0; -// while (itrChangedFields.hasNext()) { -// MappedField nextChangedField = itrChangedFields.next(); -// fieldNames[i] = nextChangedField.getField().getName(); -// i++; -// } -// accessManager.checkChangeWatchers(thePair, fieldNames, null); -// } } } - public void enqueueCheckChangeWatcher(ClassIDPair classIDPair, String[] fieldNames, String[] params, IPerceroObject oldValue){ - CheckChangeWatcherMessage message = new CheckChangeWatcherMessage(); - message.classIDPair = classIDPair; - message.fieldNames = fieldNames; - message.params = params; - if(oldValue != null) - message.oldValueJson = ((BaseDataObject)oldValue).toJson(); - template.convertAndSend("checkChangeWatcher", message); - } - - public void enqueueCheckChangeWatcher(ClassIDPair classIDPair, String[] fieldNames, String[] params){ - enqueueCheckChangeWatcher(classIDPair, fieldNames, params, null); - } - public void pushObjectUpdateJournals(Collection clientIds, ClassIDPair classIdPair, Collection changedFields) { if (classIdPair != null && clientIds != null && !clientIds.isEmpty()) { IMappedClassManager mcm = MappedClassManagerFactory.getMappedClassManager(); @@ -237,74 +198,9 @@ public void pushObjectUpdateJournals(Collection clientIds, ClassIDPair c pushUpdateResponse.getObjectList().add(object); pushSyncHelper.pushSyncResponseToClients(pushUpdateResponse, userClients); } - -// try { -// PushUpdateResponse pushUpdateResponse = new PushUpdateResponse(); -// pushUpdateResponse.setObjectList(new ArrayList()); -// -// pushUpdateResponse.setClientId(nextClientId); -// -// // Need to get version of this object appropriate for this client. -// Object object = dataProvider.findById(classIdPair, userId); -// -// if (object != null) { -// pushUpdateResponse.getObjectList().add((BaseDataObject) object); -// -// // Add changed fields names -// if (changedFields != null) { -// Iterator itrChangedFields = changedFields.iterator(); -// while (itrChangedFields.hasNext()) { -// MappedField nextChangedField = itrChangedFields.next(); -// pushUpdateResponse.addUpdatedField(nextChangedField.getField().getName()); -// } -// } -// -// pushSyncHelper.pushSyncResponseToClient(pushUpdateResponse, nextClientId); -// } -// } catch(Exception e) { -// log.error("Error pushing Object Update Journal to clients", e); -// } } } } } -// public void pushClientUpdateJournals(Map> clientUpdates) { -// -// Iterator>> itrClientUpdatesEntrySet = clientUpdates.entrySet().iterator(); -// while (itrClientUpdatesEntrySet.hasNext()) { -// Map.Entry> nextEntry = itrClientUpdatesEntrySet.next(); -// String nextClientId = nextEntry.getKey(); -// Set pairs = nextEntry.getValue(); -// -// String userId = accessManager.getClientUserId(nextClientId); -// List objectList = new ArrayList(); -// -// PushUpdateResponse pushUpdateResponse = new PushUpdateResponse(); -// pushUpdateResponse.setObjectList(objectList); -// pushUpdateResponse.setClientId(nextClientId); -// -// Iterator itrPairs = pairs.iterator(); -// while (itrPairs.hasNext()) { -// try { -// ClassIDPair pair = itrPairs.next(); -// -// IMappedClassManager mcm = MappedClassManagerFactory.getMappedClassManager(); -// MappedClass mc = mcm.getMappedClassByClassName(pair.getClassName()); -// IDataProvider dataProvider = dataProviderManager.getDataProviderByName(mc.dataProviderName); -// BaseDataObject perceroObject = (BaseDataObject) dataProvider.findById(pair, userId); -// -// if (perceroObject != null) { -// objectList.add(perceroObject); -// } -// } catch(Exception e) { -// log.error("Error pushing Object Update Journal to client " + nextClientId, e); -// } -// } -// -// if (!objectList.isEmpty()) { -// pushSyncHelper.pushSyncResponseToClient(pushUpdateResponse, nextClientId); -// } -// } -// } } diff --git a/src/main/java/com/percero/agents/sync/hibernate/SyncHibernateUtils.java b/src/main/java/com/percero/agents/sync/hibernate/SyncHibernateUtils.java index 7175938..b66489e 100644 --- a/src/main/java/com/percero/agents/sync/hibernate/SyncHibernateUtils.java +++ b/src/main/java/com/percero/agents/sync/hibernate/SyncHibernateUtils.java @@ -1,8 +1,5 @@ package com.percero.agents.sync.hibernate; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -660,59 +657,6 @@ public static Object getShell(Object object) { return object; } - @SuppressWarnings("rawtypes") - public static List getClassFields(Class theClass) { - List fieldList = new ArrayList(); - Field[] theFields = theClass.getDeclaredFields(); - - for(Field nextField : theFields) { - boolean isStatic = Modifier.STATIC == (nextField.getModifiers() & Modifier.STATIC); - if (!isStatic) - fieldList.add(nextField); - } - - if (theClass.getSuperclass() != null) - fieldList.addAll(getClassFields(theClass.getSuperclass())); - - return fieldList; - } - - @SuppressWarnings("rawtypes") - public static Method getFieldGetters(Class theClass, Field theField) { - Method theMethod = null; - Method[] theMethods = theClass.getMethods(); - String theModifiedFieldName = theField.getName(); - if (theModifiedFieldName.indexOf("_") == 0) - theModifiedFieldName = theModifiedFieldName.substring(1); - - for(Method nextMethod : theMethods) { - if (nextMethod.getName().equalsIgnoreCase("get" + theModifiedFieldName)) { - theMethod = nextMethod; - break; - } - } - - return theMethod; - } - - @SuppressWarnings("rawtypes") - public static Method getFieldSetters(Class theClass, Field theField) { - Method theMethod = null; - Method[] theMethods = theClass.getMethods(); - String theModifiedFieldName = theField.getName(); - if (theModifiedFieldName.indexOf("_") == 0) - theModifiedFieldName = theModifiedFieldName.substring(1); - - for(Method nextMethod : theMethods) { - if (nextMethod.getName().equalsIgnoreCase("set" + theModifiedFieldName)) { - theMethod = nextMethod; - break; - } - } - - return theMethod; - } - @SuppressWarnings("rawtypes") public static Object getUniqueResult(Object anObject) { if (anObject instanceof List) { diff --git a/src/main/java/com/percero/agents/sync/jobs/UpdateTableConnectionFactory.java b/src/main/java/com/percero/agents/sync/jobs/UpdateTableConnectionFactory.java index 4e0192a..97fc5cc 100644 --- a/src/main/java/com/percero/agents/sync/jobs/UpdateTableConnectionFactory.java +++ b/src/main/java/com/percero/agents/sync/jobs/UpdateTableConnectionFactory.java @@ -1,5 +1,7 @@ package com.percero.agents.sync.jobs; +import org.springframework.util.StringUtils; + import com.percero.datasource.BaseConnectionFactory; /** @@ -32,6 +34,14 @@ public void setStoredProcedureDefinition(String storedProcedureDefinition) { this.storedProcedureDefinition = storedProcedureDefinition; } + private String rowIdColumnName = "row_id"; + public String getRowIdColumnName() { + return rowIdColumnName; + } + public void setRowIdColumnName(String rowIdColumnName) { + this.rowIdColumnName = rowIdColumnName; + } + private String lockIdColumnName = "lock_id"; public String getLockIdColumnName() { return lockIdColumnName; @@ -56,11 +66,14 @@ public void setTimestampColumnName(String timestampColumnName) { this.timestampColumnName = timestampColumnName; } - private String updateStatementSql = "update :tableName set " + getLockIdColumnName() + "=:lockId, " + getLockDateColumnName() + "=NOW() " + - "where " + getLockIdColumnName() + " is null or " + - getLockDateColumnName() + " < ':expireThreshold' " + - "order by " + getTimestampColumnName() + " limit :limit"; + private String updateStatementSql = null; public String getUpdateStatementSql() { + if (!StringUtils.hasText(updateStatementSql)) { + updateStatementSql = "update :tableName set " + getLockIdColumnName() + "=:lockId, " + getLockDateColumnName() + "=NOW() " + + "where " + getLockIdColumnName() + " is null or " + + getLockDateColumnName() + " < ':expireThreshold' " + + "order by " + getTimestampColumnName() + " limit :limit"; + } return updateStatementSql; } public void setUpdateStatementSql(String updateStatementSql) { diff --git a/src/main/java/com/percero/agents/sync/jobs/UpdateTablePoller.java b/src/main/java/com/percero/agents/sync/jobs/UpdateTablePoller.java index b88274b..7647b5c 100644 --- a/src/main/java/com/percero/agents/sync/jobs/UpdateTablePoller.java +++ b/src/main/java/com/percero/agents/sync/jobs/UpdateTablePoller.java @@ -2,11 +2,10 @@ import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; -import javax.annotation.PostConstruct; - import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -54,28 +53,33 @@ public class UpdateTablePoller { @Autowired @Qualifier("executorWithCallerRunsPolicy") TaskExecutor taskExecutor; + + UpdateTableProcessReporter reporter = null; Map> runningProcessors = java.util.Collections.synchronizedMap(new HashMap>()); public boolean enabled = true; - @PostConstruct - public void init(){ - // Get the reporter going - UpdateTableProcessReporter.getInstance(); - } - /** * Run every minute */ @Scheduled(cron="0/5 * * * * *") // Every 5 seconds public void pollUpdateTables() { - logger.debug("*** UpdateTablePoller running..."); - for (UpdateTableConnectionFactory updateTableConnectionFactory : updateTableRegistry.getConnectionFactories()) { - for (String tableName : updateTableConnectionFactory.getTableNames()) { - doProcessingForTable(updateTableConnectionFactory, tableName); - } - } + List connectionFactories = updateTableRegistry.getConnectionFactories(); + + // Only attempt to process Update Tables if they are configured. + if (connectionFactories != null && !connectionFactories.isEmpty()) { + if (reporter == null) { + // Get the reporter going + reporter = UpdateTableProcessReporter.getInstance(); + } + logger.debug("*** UpdateTablePoller running..."); + for (UpdateTableConnectionFactory updateTableConnectionFactory : connectionFactories) { + for (String tableName : updateTableConnectionFactory.getTableNames()) { + doProcessingForTable(updateTableConnectionFactory, tableName); + } + } + } } @SuppressWarnings("unchecked") diff --git a/src/main/java/com/percero/agents/sync/jobs/UpdateTableProcessor.java b/src/main/java/com/percero/agents/sync/jobs/UpdateTableProcessor.java index 52111a0..bcfe405 100644 --- a/src/main/java/com/percero/agents/sync/jobs/UpdateTableProcessor.java +++ b/src/main/java/com/percero/agents/sync/jobs/UpdateTableProcessor.java @@ -34,6 +34,7 @@ import com.percero.agents.sync.metadata.MappedFieldPerceroObject; import com.percero.agents.sync.services.DataProviderManager; import com.percero.agents.sync.services.IDataProvider; +import com.percero.agents.sync.services.IPushSyncHelper; import com.percero.agents.sync.vo.BaseDataObject; import com.percero.agents.sync.vo.ClassIDPair; import com.percero.framework.bl.IManifest; @@ -54,6 +55,7 @@ public class UpdateTableProcessor implements Runnable{ protected UpdateTableConnectionFactory connectionFactory; protected PostDeleteHelper postDeleteHelper; protected PostPutHelper postPutHelper; + protected IPushSyncHelper pushSyncHelper; protected IManifest manifest; protected CacheManager cacheManager; protected DataProviderManager dataProviderManager; @@ -133,7 +135,7 @@ public void run(){ result.addResult(row.getType().toString()); successfulRows.add(row); } catch (Exception e) { - logger.warn("Failed to process update: " + e.getMessage(), e); + logger.error("Failed to process update: " + e.getMessage(), e); result.addResult(row.getType().toString(), false, e.getMessage()); releaseRowLock(row); } @@ -150,8 +152,8 @@ public void run(){ } if (!result.isSuccess()) { - logger.warn("Update table processor (" + tableName + ") failed. Details:"); - logger.warn(result); + logger.debug("Update table processor (" + tableName + ") failed. Details:"); + logger.debug(result); } }finally{ try { @@ -207,7 +209,7 @@ protected void processUpdateSingle(UpdateTableRow row) throws Exception{ List classes = getClassesForTableName(row.getTableName()); if (classes == null || classes.isEmpty()) { - logger.warn("No Classes defined for UpdateTable table " + row.getTableName()); + logger.debug("No Classes defined for UpdateTable table " + row.getTableName()); } else { logger.debug("Processing " + classes.size() + " class(es) for UpdateTable UPDATE, table " + row.getTableName()); @@ -237,7 +239,7 @@ protected void processUpdateTable(UpdateTableRow row) throws Exception{ List classes = getClassesForTableName(row.getTableName()); if (classes == null || classes.isEmpty()) { - logger.warn("No Classes defined for UpdateTable table " + row.getTableName()); + logger.debug("No Classes defined for UpdateTable table " + row.getTableName()); } else { logger.debug("Processing " + classes.size() + " class(es) for UpdateTable UPDATE, table " + row.getTableName()); @@ -346,7 +348,7 @@ protected void handleUpdateClassIdPair(IDataProvider dataProvider, ClassIDPair p fieldNames[i] = nextChangedField.getField().getName(); i++; } - postPutHelper.enqueueCheckChangeWatcher(thePair, fieldNames, null, oldValue); + pushSyncHelper.enqueueCheckChangeWatcher(thePair, fieldNames, null, oldValue); } } } @@ -373,7 +375,7 @@ protected void processInsertSingle(UpdateTableRow row) throws Exception{ List classes = getClassesForTableName(row.getTableName()); if (classes == null || classes.isEmpty()) { - logger.warn("No Classes defined for UpdateTable table " + row.getTableName()); + logger.debug("No Classes defined for UpdateTable table " + row.getTableName()); } else { logger.debug("Processing " + classes.size() + " class(es) for UpdateTable INSERT, table " + row.getTableName()); @@ -399,7 +401,7 @@ protected void processInsertTable(UpdateTableRow row) throws Exception { List classes = getClassesForTableName(row.getTableName()); if (classes == null || classes.isEmpty()) { - logger.warn("No Classes defined for UpdateTable table " + row.getTableName()); + logger.debug("No Classes defined for UpdateTable table " + row.getTableName()); } else { logger.debug("Processing " + classes.size() + " class(es) for UpdateTable INSERT, table " + row.getTableName()); @@ -432,7 +434,7 @@ protected void processDeleteSingle(UpdateTableRow row) throws Exception{ List classes = getClassesForTableName(row.getTableName()); if (classes == null || classes.isEmpty()) { - logger.warn("No Classes defined for UpdateTable table " + row.getTableName()); + logger.debug("No Classes defined for UpdateTable table " + row.getTableName()); } else { logger.debug("Processing " + classes.size() + " class(es) for UpdateTable DELETE, table " + row.getTableName()); @@ -463,7 +465,7 @@ protected void processDeleteTable(UpdateTableRow row) throws Exception{ List classes = getClassesForTableName(row.getTableName()); if (classes == null || classes.isEmpty()) { - logger.warn("No Classes defined for UpdateTable table " + row.getTableName()); + logger.debug("No Classes defined for UpdateTable table " + row.getTableName()); } else { logger.debug("Processing " + classes.size() + " class(es) for UpdateTable DELETE, table " + row.getTableName()); @@ -522,7 +524,8 @@ protected void handleDeletedObject(IPerceroObject cachedObject, Class clazz, Str cacheManager.handleDeletedObject(cachedObject, className, isShellObject); - postDeleteHelper.postDeleteObject(new ClassIDPair(id, className), null, null, true); +// postDeleteHelper.postDeleteObject(new ClassIDPair(id, className), null, null, true); + postDeleteHelper.postDeleteObject(cachedObject, null, null, true); } @SuppressWarnings("rawtypes") @@ -564,6 +567,7 @@ protected void updateReferences(String className){ // Find all of this type and push down an update to all Set ids = accessManager.getClassAccessJournalIDs(mappedField.getMappedClass().className); + logger.debug("updateReferences - ids IS NULL : " + (ids == null) ); if (ids.contains("0")) { // If there is a 0 ID in the list, then we need to update ALL records of this type. Integer pageNumber = 0; @@ -572,6 +576,7 @@ protected void updateReferences(String className){ while (total < 0 || pageNumber * pageSize <= total) { PerceroList objectsToUpdate = mappedField.getMappedClass().getDataProvider().getAllByName(mappedField.getMappedClass().className, pageNumber, pageSize, true, null); + logger.debug("updateReferences - objectsToUpdate IS NULL : " + (objectsToUpdate == null) ); pageNumber++; total = objectsToUpdate.getTotalLength(); if (total <= 0) { @@ -581,13 +586,16 @@ protected void updateReferences(String className){ Iterator itrObjectsToUpdate = objectsToUpdate.iterator(); while (itrObjectsToUpdate.hasNext()) { IDataProvider dataProvider = dataProviderManager.getDefaultDataProvider(); + logger.debug("updateReferences - dataProvider IS NULL : " + (dataProvider == null) ); // We assume this is from the cache because some client has requested all of this class // and since we turned off cache expiration it must be in there. BaseDataObject nextObjectToUpdate = (BaseDataObject) itrObjectsToUpdate.next(); + logger.debug("updateReferences - BaseDataObject IS NULL : " + (nextObjectToUpdate == null) ); ClassIDPair pair = BaseDataObject.toClassIdPair(nextObjectToUpdate); + logger.debug("updateReferences - BaseDataObject pair IS NULL : " + (pair == null) ); // This call ignores the cache BaseDataObject nextObjectToUpdateFromDB = (BaseDataObject) dataProvider.findById(pair, null, true); - + logger.debug("updateReferences - nextObjectToUpdateFromDB IS NULL : " + (nextObjectToUpdateFromDB == null) ); Map> changedFields = dataProvider .getChangedMappedFields(nextObjectToUpdateFromDB, nextObjectToUpdate, nextObjectToUpdate != null); @@ -603,14 +611,17 @@ protected void updateReferences(String className){ String nextIdToUpdate = itrIdsToUpdate.next(); ClassIDPair pair = new ClassIDPair(nextIdToUpdate, mappedField.getMappedClass().className); - IDataProvider dataProvider = dataProviderManager.getDefaultDataProvider(); + logger.debug("updateReferences - ELSE pair IS NULL : " + (pair == null) ); + IDataProvider dataProvider = dataProviderManager.getDefaultDataProvider(); + logger.debug("updateReferences - ELSE dataProvider IS NULL : " + (dataProvider == null) ); // First call assumes we are getting it from the cache... because we can assume that // All objects that we care about are all cached (since we turned off cache expiration). BaseDataObject cachedObject = (BaseDataObject) dataProvider.findById(pair, null); + logger.debug("updateReferences - ELSE cachedObject IS NULL : " + (cachedObject == null) ); // This call ignores the cache BaseDataObject objectFromDB = (BaseDataObject) dataProvider.findById(pair, null, true); - + logger.debug("updateReferences - ELSE BaseDataObject objectFromDB IS NULL : " + (objectFromDB == null) ); Map> changedFields = dataProvider .getChangedMappedFields(objectFromDB, cachedObject, cachedObject != null); // Only push if different @@ -715,7 +726,7 @@ private List getUpdateSelectRow(int lockId, DateTime expireThres } if(count != numUpdated) - logger.warn("Locked a "+numUpdated+" rows but found "+count); + logger.debug("Locked a "+numUpdated+" rows but found "+count); } } } @@ -741,7 +752,7 @@ private List getStoredProcRow(int lockId, DateTime expireThresho updateNum = cstmt.getInt(4); } catch(SQLException e){ // return null; - logger.warn(e.getMessage(), e); + logger.error(e.getMessage(), e); // If the stored proc doesn't exist, let's try and create it. if (StringUtils.hasText(connectionFactory.getStoredProcedureDefinition()) && @@ -754,11 +765,11 @@ private List getStoredProcRow(int lockId, DateTime expireThresho System.out.println(createResult); } catch(SQLSyntaxErrorException ssee) { - logger.warn("Unable to create UpdateTable stored procedure: " + ssee.getMessage()); + logger.error("Unable to create UpdateTable stored procedure: " + ssee.getMessage()); throw ssee; } catch(Exception e1) { - logger.warn("Unable to create UpdateTable stored procedure: " + e1.getMessage()); + logger.error("Unable to create UpdateTable stored procedure: " + e1.getMessage()); throw e1; } } @@ -779,7 +790,7 @@ private List getStoredProcRow(int lockId, DateTime expireThresho } if(count != updateNum) - logger.warn("Locked a "+updateNum+" rows but found "+count); + logger.error("Locked a "+updateNum+" rows but found "+count); } } @@ -800,10 +811,11 @@ protected void deleteRows(List rows){ int numUpdated = statement.executeUpdate(sql); if(numUpdated != rows.size()){ - logger.warn("Expected to delete "+rows.size()+", instead "+numUpdated); + //As discussed with Richard, commenting the log +// logger.warn("Expected to delete "+rows.size()+", instead "+numUpdated); } }catch(SQLException e){ - logger.warn(e.getMessage(), e); + logger.error(e.getMessage(), e); } } @@ -817,10 +829,11 @@ protected void releaseRowLock(UpdateTableRow row){ int numUpdated = statement.executeUpdate(sql); if(numUpdated != 1){ - logger.warn("Expected to update 1, instead "+numUpdated); + //As discussed with Richard, commenting the log +// logger.warn("Expected to update 1, instead "+numUpdated); } }catch(SQLException e){ - logger.warn(e.getMessage(), e); + logger.error(e.getMessage(), e); } } @@ -857,13 +870,13 @@ protected UpdateTableRow fromResultSet(ResultSet resultSet) throws SQLException{ UpdateTableRow row = new UpdateTableRow(); row.ID = resultSet.getInt("ID"); row.tableName = resultSet.getString("tableName"); - row.rowId = resultSet.getString("row_id"); + row.rowId = resultSet.getString(connectionFactory.getRowIdColumnName()); row.lockId = resultSet.getInt(connectionFactory.getLockIdColumnName()); row.lockDate = resultSet.getDate(connectionFactory.getLockDateColumnName()); try { row.type = UpdateTableRowType.valueOf(resultSet.getString("type")); } catch(IllegalArgumentException iae) { - logger.warn("Invalid UpdateTableRow TYPE, ignoring"); + logger.error("Invalid UpdateTableRow TYPE, ignoring"); row.type = UpdateTableRowType.NONE; } row.timestamp = resultSet.getDate(connectionFactory.getTimestampColumnName()); diff --git a/src/main/java/com/percero/agents/sync/metadata/MappedClass.java b/src/main/java/com/percero/agents/sync/metadata/MappedClass.java index d3657ba..c93d5d8 100644 --- a/src/main/java/com/percero/agents/sync/metadata/MappedClass.java +++ b/src/main/java/com/percero/agents/sync/metadata/MappedClass.java @@ -1,5 +1,6 @@ package com.percero.agents.sync.metadata; +import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; @@ -35,7 +36,6 @@ import com.percero.agents.sync.annotations.PerceroNamedNativeQueries; import com.percero.agents.sync.annotations.PerceroNamedNativeQuery; -import com.percero.agents.sync.hibernate.SyncHibernateUtils; import com.percero.agents.sync.metadata.annotations.AccessRight; import com.percero.agents.sync.metadata.annotations.AccessRights; import com.percero.agents.sync.metadata.annotations.DataProvider; @@ -56,6 +56,8 @@ import com.percero.framework.metadata.IMappedClass; import com.percero.framework.metadata.IMappedQuery; import com.percero.framework.vo.IPerceroObject; +import com.percero.util.MappedClassUtils; +import com.sun.org.apache.xerces.internal.dom.EntityImpl; @SuppressWarnings("unchecked") public class MappedClass implements IMappedClass { @@ -141,6 +143,37 @@ public static List findEntityImplementation( return entityInterfacesMappedClasses.get(interfaceClazz); } + @SuppressWarnings("rawtypes") + public static List findRootEntityImplementation( + Class interfaceClazz) { + List entityImplementations = entityInterfacesMappedClasses.get(interfaceClazz); + List result = new ArrayList(); + + for(EntityImplementation entityImpl : entityImplementations) { + MappedClass mc = entityImpl.mappedClass; + if (mc == null || mc.parentMappedClass == null) { + // No MappedClass or parent MappedClass. + result.add(entityImpl); + continue; + } + + // If the entityImpl's parent is in the list of Entity Implementations, then this is NOT a root implementation. + boolean entityImplsParentIsEntityImpl = false; + for(EntityImplementation nextEntityImpl : entityImplementations) { + if (nextEntityImpl.mappedClass == mc.parentMappedClass) { + entityImplsParentIsEntityImpl = true; + break; + } + } + + if (!entityImplsParentIsEntityImpl) { + result.add(entityImpl); + } + } + + return result; + } + private static Comparator fieldComparator; static { @@ -228,9 +261,9 @@ public Set getTargetMappedFields() { .synchronizedSet(new HashSet()); public Set externalizableFields = Collections .synchronizedSet(new TreeSet(fieldComparator)); - public Map cascadeRemoveFieldReferences = Collections + private Map cascadeRemoveFieldReferences = Collections .synchronizedMap(new HashMap()); - public Map nulledOnRemoveFieldReferences = Collections + private Map nulledOnRemoveFieldReferences = Collections .synchronizedMap(new HashMap()); @SuppressWarnings("rawtypes") public Map entityImplementations = Collections @@ -415,40 +448,68 @@ protected EntityImplementation processEntityInterface( entityImplementations.put(entityInterface.interfaceClass(), entityImpl); return entityImpl; } - + @SuppressWarnings("rawtypes") + protected MappedField handleCreateMappedFieldFromClass(Class fieldClass) { + MappedField newMappedField = null; + if (fieldClass == int.class) + newMappedField = new MappedFieldInt(); + else if (fieldClass == Integer.class) + newMappedField = new MappedFieldInteger(); + else if (fieldClass == float.class) + newMappedField = new MappedFieldFloat(); + else if (fieldClass == Float.class) + newMappedField = new MappedFieldFloat(); + else if (fieldClass == double.class) + newMappedField = new MappedFieldDouble(); + else if (fieldClass == Double.class) + newMappedField = new MappedFieldDouble(); + else if (fieldClass == boolean.class) + newMappedField = new MappedFieldBool(); + else if (fieldClass == Boolean.class) + newMappedField = new MappedFieldBoolean(); + else if (fieldClass == String.class) + newMappedField = new MappedFieldString(); + else if (fieldClass == Date.class) + newMappedField = new MappedFieldDate(); + /** + * else if (nextFieldClass == Map.class) nextMappedField = new + * MappedFieldMap(); else if (nextFieldClass == List.class) + * nextMappedField = new MappedFieldList(); + **/ + else if (implementsInterface(fieldClass, IPerceroObject.class)) { + newMappedField = new MappedFieldPerceroObject(); + externalizablePerceroObjectFields.add((MappedFieldPerceroObject) newMappedField); + } + else if (implementsInterface(fieldClass, Map.class)) + newMappedField = new MappedFieldMap(); + else if (implementsInterface(fieldClass, List.class)) + newMappedField = new MappedFieldList(); + + else + newMappedField = new MappedField(); + + return newMappedField; + } + public void initializeFields() { if (fieldsInitialized) { return; } try { - Class clazz = MappedClass.forName(className); + if (clazz == null) { + clazz = MappedClass.forName(className); + } - List fields = SyncHibernateUtils.getClassFields(clazz); + List fields = MappedClassUtils.getClassFields(clazz); for (Field nextField : fields) { - // Ignore this field if marked as Transient. - Transient transientAnno = (Transient) nextField - .getAnnotation(Transient.class); - if (transientAnno != null) - continue; - - Externalize externalize = (Externalize) nextField - .getAnnotation(Externalize.class); - if (externalize == null) { - // Only process Externalizeable fields. - continue; - } - // Check to see if this Field has already been processed. - Iterator itrExternalizeFields = externalizableFields - .iterator(); + Iterator itrExternalizeFields = externalizableFields.iterator(); Boolean fieldAlreadyMapped = false; while (itrExternalizeFields.hasNext()) { - MappedField nextExistingMappedField = itrExternalizeFields - .next(); - if (nextExistingMappedField.getField().getName() - .equals(nextField.getName())) { + MappedField nextExistingMappedField = itrExternalizeFields.next(); + if (nextExistingMappedField.getField().getName().equals(nextField.getName())) { // This field has already been mapped. fieldAlreadyMapped = true; break; @@ -459,57 +520,26 @@ public void initializeFields() { continue; } - Method theGetter = SyncHibernateUtils.getFieldGetters(clazz, - nextField); - transientAnno = (Transient) theGetter - .getAnnotation(Transient.class); - if (transientAnno != null) + Method theGetter = MappedClassUtils.getFieldGetters(clazz, nextField); + + // Ignore this field if marked as Transient. + if (hasAnnotation(nextField, theGetter, Transient.class)) continue; - Method theSetter = SyncHibernateUtils.getFieldSetters(clazz, - nextField); - - MappedField nextMappedField = null; - Class nextFieldClass = nextField.getType(); - if (nextFieldClass == int.class) - nextMappedField = new MappedFieldInt(); - else if (nextFieldClass == Integer.class) - nextMappedField = new MappedFieldInteger(); - else if (nextFieldClass == float.class) - nextMappedField = new MappedFieldFloat(); - else if (nextFieldClass == Float.class) - nextMappedField = new MappedFieldFloat(); - else if (nextFieldClass == double.class) - nextMappedField = new MappedFieldDouble(); - else if (nextFieldClass == Double.class) - nextMappedField = new MappedFieldDouble(); - else if (nextFieldClass == boolean.class) - nextMappedField = new MappedFieldBool(); - else if (nextFieldClass == Boolean.class) - nextMappedField = new MappedFieldBoolean(); - else if (nextFieldClass == String.class) - nextMappedField = new MappedFieldString(); - else if (nextFieldClass == Date.class) - nextMappedField = new MappedFieldDate(); - /** - * else if (nextFieldClass == Map.class) nextMappedField = new - * MappedFieldMap(); else if (nextFieldClass == List.class) - * nextMappedField = new MappedFieldList(); - **/ - else if (implementsInterface(nextFieldClass, - IPerceroObject.class)) - nextMappedField = new MappedFieldPerceroObject(); - else if (implementsInterface(nextFieldClass, Map.class)) - nextMappedField = new MappedFieldMap(); - else if (implementsInterface(nextFieldClass, List.class)) - nextMappedField = new MappedFieldList(); - - else - nextMappedField = new MappedField(); + Externalize externalize = retrieveAnnotation(nextField, theGetter, Externalize.class); + if (externalize == null) { + // Only process Externalizeable fields. + continue; + } + + Method theSetter = MappedClassUtils.getFieldSetters(clazz, nextField); + + MappedField nextMappedField = handleCreateMappedFieldFromClass(nextField.getType()); nextMappedField.setMappedClass(this); nextMappedField.setField(nextField); nextMappedField.setGetter(theGetter); nextMappedField.setSetter(theSetter); + externalizableFields.add(nextMappedField); nextMappedField.setUseLazyLoading(externalize.useLazyLoading()); if (!nextMappedField.getUseLazyLoading()) @@ -517,53 +547,14 @@ else if (implementsInterface(nextFieldClass, List.class)) if (!externalize.useLazyLoading()) nonLazyLoadingFields.add(nextMappedField); - externalizableFields.add(nextMappedField); - if (nextMappedField instanceof MappedFieldPerceroObject) - externalizablePerceroObjectFields - .add((MappedFieldPerceroObject) nextMappedField); - - OneToMany oneToMany = (OneToMany) theGetter - .getAnnotation(OneToMany.class); - if (oneToMany == null) - oneToMany = (OneToMany) nextField - .getAnnotation(OneToMany.class); - if (oneToMany != null) { - toManyFields.add(nextMappedField); - getTargetMappedFields().add(nextMappedField); - } - - if (oneToMany != null) { - ((MappedFieldList) nextMappedField).setListClass(oneToMany.targetEntity()); - } - ManyToOne manyToOne = (ManyToOne) theGetter - .getAnnotation(ManyToOne.class); - if (manyToOne == null) - manyToOne = (ManyToOne) nextField - .getAnnotation(ManyToOne.class); - if (manyToOne != null) { - getSourceMappedFields().add( - (MappedFieldPerceroObject) nextMappedField); - } - - OneToOne oneToOne = (OneToOne) theGetter - .getAnnotation(OneToOne.class); - if (oneToOne == null) - oneToOne = (OneToOne) nextField - .getAnnotation(OneToOne.class); - if (oneToOne != null) { - if (StringUtils.hasText(oneToOne.mappedBy())) { - getTargetMappedFields().add(nextMappedField); - } else { - getSourceMappedFields().add( - (MappedFieldPerceroObject) nextMappedField); - } - } + handleAnnotation_OneToMany(nextField, theGetter, nextMappedField); + handleAnnotation_ManyToOne(nextField, theGetter, nextMappedField); + handleAnnotation_OneToOne(nextField, theGetter, nextMappedField); Boolean isPropertyField = true; - Entity nextEntity = (Entity) nextField.getType().getAnnotation( - Entity.class); - if (nextEntity != null) { + if (nextField.getType().isAnnotationPresent(Entity.class)) { +// if (hasAnnotation(nextField.getType(), null, Entity.class)) { entityFields.add(nextField); toOneFields.add((MappedFieldPerceroObject) nextMappedField); isPropertyField = false; @@ -585,153 +576,292 @@ else if (implementsInterface(nextFieldClass, List.class)) propertyFields.add(nextMappedField); } - Id id = (Id) theGetter.getAnnotation(Id.class); - if (id == null) - id = (Id) nextField.getAnnotation(Id.class); - if (id != null) { - idMappedField = nextMappedField; - - List uniqueConstraintList = new ArrayList( - 1); - uniqueConstraintList.add(nextMappedField); - uniqueConstraints.add(uniqueConstraintList); - - // Check to see if this class has a Generated ID. - GeneratedValue generatedValue = (GeneratedValue) nextField - .getAnnotation(GeneratedValue.class); - hasGeneratedId = (generatedValue != null); - } + handleAnnotation_Id(nextField, theGetter, nextMappedField); + handleAnnotation_Column(nextField, theGetter, nextMappedField); + handleAnnotation_JoinColumn(nextField, theGetter, nextMappedField); + handleAnnotation_AccessRights(nextField, theGetter, nextMappedField); - Column column = (Column) theGetter.getAnnotation(Column.class); - if (column == null) - column = (Column) nextField.getAnnotation(Column.class); - if (column != null) { - if (column.unique()) { - List uniqueConstraintList = new ArrayList( - 1); - uniqueConstraintList.add(nextMappedField); - uniqueConstraints.add(uniqueConstraintList); - } + // Check to see if this has any PropertyInterfaces that need to + // be addressed. + handleAnnotation_PropertyInterface(nextField, theGetter, nextMappedField); + handleAnnotation_PropertyInterfaces(nextField, theGetter, nextMappedField); + } + } catch (Exception e) { + logger.error("Error parsing MappedClass " + this.className, e); + } + fieldsInitialized = true; + } - if (column.name() != null - && column.name().trim().length() > 0) - nextMappedField.setColumnName(column.name()); - else - nextMappedField.setColumnName(nextField.getName()); - } + /** + * @param nextField + * @param theGetter + * @param nextMappedField + * @return + */ + protected PropertyInterfaces handleAnnotation_PropertyInterfaces(Field nextField, Method theGetter, + MappedField nextMappedField) { + PropertyInterfaces propertyInterfaces = retrieveAnnotation(nextField, theGetter, PropertyInterfaces.class); + if (propertyInterfaces != null) { + for (PropertyInterface nextPropInterface : propertyInterfaces.propertyInterfaces()) { + processMappedFieldPropertyInterface(nextPropInterface, nextMappedField); + } + } + return propertyInterfaces; + } - JoinColumn joinColumn = (JoinColumn) theGetter - .getAnnotation(JoinColumn.class); - if (joinColumn == null) - joinColumn = (JoinColumn) nextField - .getAnnotation(JoinColumn.class); - if (joinColumn != null) { - if (StringUtils.hasText(joinColumn.name())) { - nextMappedField.setJoinColumnName(joinColumn.name()); - } - } + /** + * @param nextField + * @param theGetter + * @param nextMappedField + * @return + */ + protected PropertyInterface handleAnnotation_PropertyInterface(Field nextField, Method theGetter, + MappedField nextMappedField) { + PropertyInterface propInterface = retrieveAnnotation(nextField, theGetter, PropertyInterface.class); + if (propInterface != null) { + processMappedFieldPropertyInterface(propInterface, nextMappedField); + } + return propInterface; + } - // Get NamedQueries for handling Access Rights. - AccessRights accessRights = (AccessRights) nextField - .getAnnotation(AccessRights.class); - if (accessRights == null) - accessRights = (AccessRights) nextMappedField.getGetter() - .getAnnotation(AccessRights.class); - if (accessRights != null) { - for (AccessRight nextAccessRight : accessRights.value()) { - /* - * if - * (nextAccessRight.type().equalsIgnoreCase("createQuery" - * )) { if (nextAccessRight.query().indexOf("jpql:") >= - * 0) nextMappedField.createQuery = new JpqlQuery(); - * else nextMappedField.createQuery = new MappedQuery(); - * nextMappedField - * .createQuery.setQuery(nextAccessRight.query()); } - * else if - * (nextAccessRight.type().equalsIgnoreCase("updateQuery" - * )) { if (nextAccessRight.query().indexOf("jpql:") >= - * 0) nextMappedField.updateQuery = new JpqlQuery(); - * else nextMappedField.updateQuery = new MappedQuery(); - * nextMappedField - * .updateQuery.setQuery(nextAccessRight.query()); } - * else - */ - if (nextAccessRight.type() - .equalsIgnoreCase("readQuery")) { - if (nextAccessRight.query().indexOf("jpql:") >= 0) { - nextMappedField.setReadQuery(new JpqlQuery()); - nextMappedField.getReadQuery().setQuery( - nextAccessRight.query()); - } else if (nextAccessRight.query().indexOf("sql:") >= 0) { - nextMappedField.setReadQuery(new SqlQuery( - nextAccessRight.query().substring( - nextAccessRight.query() - .indexOf("sql:") + 4))); - } else { - // Whatever type of Query this is, it is not - // supported. - continue; - // nextMappedField.setReadQuery(new - // MappedQuery()); - } - nextMappedField.setHasReadAccessRights(true); - readAccessRightsFieldReferences - .add(nextMappedField); - } /* - * else if - * (nextAccessRight.type().equalsIgnoreCase("deleteQuery" - * )) { if (nextAccessRight.query().indexOf("jpql:") >= - * 0) nextMappedField.deleteQuery = new JpqlQuery(); - * else nextMappedField.deleteQuery = new MappedQuery(); - * nextMappedField - * .deleteQuery.setQuery(nextAccessRight.query()); } - */ + /** + * @param nextField + * @param theGetter + * @param nextMappedField + * @return + */ + protected OneToOne handleAnnotation_OneToOne(Field nextField, Method theGetter, MappedField nextMappedField) { + OneToOne oneToOne = retrieveAnnotation(nextField, theGetter, OneToOne.class); + if (oneToOne != null) { + if (StringUtils.hasText(oneToOne.mappedBy())) { + getTargetMappedFields().add(nextMappedField); + } else { + getSourceMappedFields().add((MappedFieldPerceroObject) nextMappedField); + } + } + return oneToOne; + } - // Add to queries list. - IMappedQuery nextQuery = null; - if (nextAccessRight.query().indexOf("jpql:") >= 0) { - nextQuery = new JpqlQuery(); - nextQuery.setQuery(nextAccessRight.query()); - } else if (nextAccessRight.query().indexOf("sql:") >= 0) { - nextQuery = new SqlQuery(nextAccessRight.query() - .substring( - nextAccessRight.query().indexOf( - "sql:") + 4)); - } else { - // Unsupported Query type - continue; - // nextQuery = new MappedQuery(); - } - nextQuery.setQueryName(nextAccessRight.type()); + /** + * @param nextField + * @param theGetter + * @param nextMappedField + */ + protected void handleAnnotation_ManyToOne(Field nextField, Method theGetter, MappedField nextMappedField) { + if (hasAnnotation(nextField, theGetter, ManyToOne.class)) { + getSourceMappedFields().add((MappedFieldPerceroObject) nextMappedField); + } + } - nextMappedField.queries.add(nextQuery); - } - } + /** + * @param nextField + * @param theGetter + * @param nextMappedField + * @return + */ + protected OneToMany handleAnnotation_OneToMany(Field nextField, Method theGetter, MappedField nextMappedField) { + OneToMany oneToMany = retrieveAnnotation(nextField, theGetter, OneToMany.class); + if (oneToMany != null) { + toManyFields.add(nextMappedField); + getTargetMappedFields().add(nextMappedField); + ((MappedFieldList) nextMappedField).setListClass(oneToMany.targetEntity()); + } + return oneToMany; + } - // Check to see if this has any PropertyInterfaces that need to - // be addresed. - PropertyInterface propInterface = (PropertyInterface) nextField - .getAnnotation(PropertyInterface.class); - if (propInterface != null) { - processMappedFieldPropertyInterface(propInterface, - nextMappedField); - } - PropertyInterfaces propertyInterfaces = (PropertyInterfaces) nextField - .getAnnotation(PropertyInterfaces.class); - if (propertyInterfaces != null) { - for (PropertyInterface nextPropInterface : propertyInterfaces - .propertyInterfaces()) { - processMappedFieldPropertyInterface(nextPropInterface, - nextMappedField); + /** + * @param nextField + * @param theGetter + * @param nextMappedField + */ + protected void handleAnnotation_Id(Field nextField, Method theGetter, MappedField nextMappedField) { + if (hasAnnotation(nextField, theGetter, Id.class)) { + idMappedField = nextMappedField; + + List uniqueConstraintList = new ArrayList(1); + uniqueConstraintList.add(nextMappedField); + uniqueConstraints.add(uniqueConstraintList); + + // Check to see if this class has a Generated ID. + hasGeneratedId = hasAnnotation(nextField, theGetter, GeneratedValue.class); + } + } + + /** + * @param nextField + * @param theGetter + * @param nextMappedField + * @return + */ + protected JoinColumn handleAnnotation_JoinColumn(Field nextField, Method theGetter, MappedField nextMappedField) { + JoinColumn joinColumn = retrieveAnnotation(nextField, theGetter, JoinColumn.class); + if (joinColumn != null) { + if (StringUtils.hasText(joinColumn.name())) { + nextMappedField.setJoinColumnName(joinColumn.name()); + } + } + return joinColumn; + } + + /** + * @param nextField + * @param theGetter + * @param nextMappedField + * @return + */ + protected Column handleAnnotation_Column(Field nextField, Method theGetter, MappedField nextMappedField) { + Column column = retrieveAnnotation(nextField, theGetter, Column.class); + if (column != null) { + if (column.unique()) { + List uniqueConstraintList = new ArrayList(1); + uniqueConstraintList.add(nextMappedField); + uniqueConstraints.add(uniqueConstraintList); + } + + if (column.name() != null && column.name().trim().length() > 0) + nextMappedField.setColumnName(column.name()); + else + nextMappedField.setColumnName(nextField.getName()); + } + return column; + } + + /** + * @param nextField + * @param theGetter + * @param nextMappedField + * @return + */ + protected AccessRights handleAnnotation_AccessRights(Field nextField, Method theGetter, MappedField nextMappedField) { + AccessRights accessRights = retrieveAnnotation(nextField, theGetter, AccessRights.class); + if (accessRights != null) { + // Get NamedQueries for handling Access Rights. + for (AccessRight nextAccessRight : accessRights.value()) { + /* + * if + * (nextAccessRight.type().equalsIgnoreCase("createQuery" + * )) { if (nextAccessRight.query().indexOf("jpql:") >= + * 0) nextMappedField.createQuery = new JpqlQuery(); + * else nextMappedField.createQuery = new MappedQuery(); + * nextMappedField + * .createQuery.setQuery(nextAccessRight.query()); } + * else if + * (nextAccessRight.type().equalsIgnoreCase("updateQuery" + * )) { if (nextAccessRight.query().indexOf("jpql:") >= + * 0) nextMappedField.updateQuery = new JpqlQuery(); + * else nextMappedField.updateQuery = new MappedQuery(); + * nextMappedField + * .updateQuery.setQuery(nextAccessRight.query()); } + * else + */ + if (nextAccessRight.type() + .equalsIgnoreCase("readQuery")) { + if (nextAccessRight.query().indexOf("jpql:") >= 0) { + nextMappedField.setReadQuery(new JpqlQuery()); + nextMappedField.getReadQuery().setQuery( + nextAccessRight.query()); + } else if (nextAccessRight.query().indexOf("sql:") >= 0) { + nextMappedField.setReadQuery(new SqlQuery( + nextAccessRight.query().substring( + nextAccessRight.query() + .indexOf("sql:") + 4))); + } else { + // Whatever type of Query this is, it is not + // supported. + continue; + // nextMappedField.setReadQuery(new + // MappedQuery()); } + nextMappedField.setHasReadAccessRights(true); + readAccessRightsFieldReferences + .add(nextMappedField); + } /* + * else if + * (nextAccessRight.type().equalsIgnoreCase("deleteQuery" + * )) { if (nextAccessRight.query().indexOf("jpql:") >= + * 0) nextMappedField.deleteQuery = new JpqlQuery(); + * else nextMappedField.deleteQuery = new MappedQuery(); + * nextMappedField + * .deleteQuery.setQuery(nextAccessRight.query()); } + */ + + // Add to queries list. + IMappedQuery nextQuery = null; + if (nextAccessRight.query().indexOf("jpql:") >= 0) { + nextQuery = new JpqlQuery(); + nextQuery.setQuery(nextAccessRight.query()); + } else if (nextAccessRight.query().indexOf("sql:") >= 0) { + nextQuery = new SqlQuery(nextAccessRight.query() + .substring( + nextAccessRight.query().indexOf( + "sql:") + 4)); + } else { + // Unsupported Query type + continue; + // nextQuery = new MappedQuery(); } + nextQuery.setQueryName(nextAccessRight.type()); + + nextMappedField.queries.add(nextQuery); } - } catch (Exception e) { - logger.error("Error parsing MappedClass " + this.className, e); } - fieldsInitialized = true; + return accessRights; + } + + /** + * @param mappedField + * @param annotationClass + * @return + */ + private T retrieveAnnotation(MappedField mappedField, Class annotationClass) { + if (mappedField != null) { + return retrieveAnnotation(mappedField.getField(), mappedField.getGetter(), annotationClass); + } + else { + return null; + } + } + + /** + * @param nextField + * @param theGetter + * @param annotationClass + * @return + */ + private T retrieveAnnotation(Field nextField, Method theGetter, Class annotationClass) { + T annotation = (theGetter != null ? theGetter.getAnnotation(annotationClass) : null); + if (annotation == null) + annotation = (nextField != null ? nextField.getAnnotation(annotationClass) : null); + return annotation; } + /** + * @param mappedField + * @param annotationClass + * @return + */ + private boolean hasAnnotation(MappedField mappedField, Class annotationClass) { + if (mappedField != null) { + return hasAnnotation(mappedField.getField(), mappedField.getGetter(), annotationClass); + } + else { + return false; + } + } + /** + * @param nextField + * @param theGetter + * @param annotationClass + * @return + */ + @SuppressWarnings("rawtypes") + private boolean hasAnnotation(Field nextField, Method theGetter, Class annotationClass) { + boolean result = (theGetter != null ? theGetter.isAnnotationPresent(annotationClass) : false); + if (!result) + result = (nextField != null ? nextField.isAnnotationPresent(annotationClass) : false); + return result; + } + private void processMappedFieldPropertyInterface( PropertyInterface propInterface, MappedField mappedField) { if (propInterface == null @@ -879,13 +1009,7 @@ public void initializeQueries() { // Need to find the corresponding field. for (MappedField nextRefMappedField : referencedMappedClass.toManyFields) { if (nextRefMappedField instanceof MappedFieldList) { - OneToMany refOneToMany = nextRefMappedField - .getField().getAnnotation( - OneToMany.class); - if (refOneToMany == null) - refOneToMany = nextRefMappedField - .getGetter().getAnnotation( - OneToMany.class); + OneToMany refOneToMany = retrieveAnnotation(nextRefMappedField, OneToMany.class); if (refOneToMany != null && refOneToMany.targetEntity() @@ -904,13 +1028,7 @@ public void initializeQueries() { break; } } else if (nextRefMappedField instanceof MappedFieldPerceroObject) { - OneToOne refOneToOne = nextRefMappedField - .getField().getAnnotation( - OneToOne.class); - if (refOneToOne == null) - refOneToOne = nextRefMappedField - .getGetter().getAnnotation( - OneToOne.class); + OneToOne refOneToOne = retrieveAnnotation(nextRefMappedField, OneToOne.class); if (refOneToOne != null && refOneToOne.targetEntity() @@ -1003,13 +1121,7 @@ public void initializeRelationships() { while (itrExternalizeFields.hasNext()) { MappedField nextMappedField = itrExternalizeFields.next(); - OneToMany oneToMany = (OneToMany) nextMappedField.getGetter() - .getAnnotation(OneToMany.class); - if (oneToMany == null) - oneToMany = (OneToMany) nextMappedField.getField() - .getAnnotation(OneToMany.class); - - if (oneToMany != null) { + if (hasAnnotation(nextMappedField, OneToMany.class)) { // // This must be a source MappedField // sourceMappedFields.add((MappedFieldPerceroObject) // nextMappedField); @@ -1034,35 +1146,8 @@ public void initializeRelationships() { } - ManyToOne manyToOne = (ManyToOne) nextMappedField.getGetter() - .getAnnotation(ManyToOne.class); - if (manyToOne == null) - manyToOne = (ManyToOne) nextMappedField.getField() - .getAnnotation(ManyToOne.class); - - // if (manyToOne != null) { - // // This must be a target MappedField - // targetMappedFields.add(nextMappedField); - // } - - OneToOne oneToOne = (OneToOne) nextMappedField.getGetter() - .getAnnotation(OneToOne.class); - if (oneToOne == null) - oneToOne = (OneToOne) nextMappedField.getField() - .getAnnotation(OneToOne.class); - - // if (oneToOne != null) { - // // Not sure if this is source or target, let's find out... - // if(StringUtils.hasText(oneToOne.mappedBy())) { - // // This must be a target MappedField - // targetMappedFields.add(nextMappedField); - // } - // else { - // // This must be a source MappedField - // sourceMappedFields.add((MappedFieldPerceroObject) - // nextMappedField); - // } - // } + ManyToOne manyToOne = retrieveAnnotation(nextMappedField, ManyToOne.class); + OneToOne oneToOne = retrieveAnnotation(nextMappedField, OneToOne.class); if (manyToOne != null && !manyToOne.optional() || oneToOne != null && !oneToOne.optional()) { @@ -1087,20 +1172,13 @@ public void initializeRelationships() { // Find the reverse field. for (MappedField nextRefMappedField : referencedMappedClass.toManyFields) { if (nextRefMappedField instanceof MappedFieldList) { - OneToMany refOneToMany = nextRefMappedField - .getField().getAnnotation(OneToMany.class); - if (refOneToMany == null) - refOneToMany = nextRefMappedField.getGetter() - .getAnnotation(OneToMany.class); + OneToMany refOneToMany = retrieveAnnotation(nextRefMappedField, OneToMany.class); if (refOneToMany != null) { - Boolean inheritsFrom = - inheritsFrom(this.clazz, - refOneToMany.targetEntity()); - if (inheritsFrom && - nextMappedField.getField().getName().equals(refOneToMany.mappedBy())) - { + Boolean inheritsFrom = inheritsFrom(this.clazz, refOneToMany.targetEntity()); + if (inheritsFrom + && nextMappedField.getField().getName().equals(refOneToMany.mappedBy())) { // if (this.clazz == refOneToMany.targetEntity() // && nextMappedField // .getField() @@ -1112,11 +1190,7 @@ public void initializeRelationships() { } } } else if (nextRefMappedField instanceof MappedFieldPerceroObject) { - OneToOne refOneToOne = nextRefMappedField - .getField().getAnnotation(OneToOne.class); - if (refOneToOne == null) - refOneToOne = nextRefMappedField.getGetter() - .getAnnotation(OneToOne.class); + OneToOne refOneToOne = retrieveAnnotation(nextRefMappedField, OneToOne.class); if (refOneToOne != null) { Boolean inheritsFrom = @@ -1140,13 +1214,7 @@ public void initializeRelationships() { // Find the reverse field. for (MappedField nextRefMappedField : referencedMappedClass.toOneFields) { if (nextRefMappedField instanceof MappedFieldPerceroObject) { - OneToOne refOneToOne = nextRefMappedField - .getField().getAnnotation( - OneToOne.class); - if (refOneToOne == null) - refOneToOne = nextRefMappedField - .getGetter().getAnnotation( - OneToOne.class); + OneToOne refOneToOne = retrieveAnnotation(nextRefMappedField, OneToOne.class); if (refOneToOne != null) { if (StringUtils.hasText(refOneToOne.mappedBy())) { @@ -1194,7 +1262,7 @@ public void initializeRelationships() { } } } - referencedMappedClass.cascadeRemoveFieldReferences.put( + referencedMappedClass.addCascadeRemoveFieldReferences( nextMappedField, reverseMappedField); } @@ -1220,11 +1288,7 @@ public void initializeRelationships() { // Find the reverse field. for (MappedField nextRefMappedField : referencedMappedClass.toManyFields) { if (nextRefMappedField instanceof MappedFieldList) { - OneToMany refOneToMany = nextRefMappedField - .getField().getAnnotation(OneToMany.class); - if (refOneToMany == null) - refOneToMany = nextRefMappedField.getGetter() - .getAnnotation(OneToMany.class); + OneToMany refOneToMany = retrieveAnnotation(nextRefMappedField, OneToMany.class); if (refOneToMany != null) { Boolean inheritsFrom = @@ -1244,11 +1308,7 @@ public void initializeRelationships() { } } } else if (nextRefMappedField instanceof MappedFieldPerceroObject) { - OneToOne refOneToOne = nextRefMappedField - .getField().getAnnotation(OneToOne.class); - if (refOneToOne == null) - refOneToOne = nextRefMappedField.getGetter() - .getAnnotation(OneToOne.class); + OneToOne refOneToOne = retrieveAnnotation(nextRefMappedField, OneToOne.class); if (refOneToOne != null) { if (StringUtils.hasText(refOneToOne.mappedBy())) { @@ -1282,13 +1342,7 @@ public void initializeRelationships() { // Find the reverse field. for (MappedField nextRefMappedField : referencedMappedClass.toOneFields) { if (nextRefMappedField instanceof MappedFieldPerceroObject) { - OneToOne refOneToOne = nextRefMappedField - .getField().getAnnotation( - OneToOne.class); - if (refOneToOne == null) - refOneToOne = nextRefMappedField - .getGetter().getAnnotation( - OneToOne.class); + OneToOne refOneToOne = retrieveAnnotation(nextRefMappedField, OneToOne.class); if (refOneToOne != null) { // Boolean inheritsFrom = @@ -1297,13 +1351,8 @@ public void initializeRelationships() { // if (inheritsFrom && // nextMappedField.getField().getName().equals(refOneToOne.mappedBy())) // { - if (this.clazz == nextRefMappedField - .getField().getType() - && nextMappedField - .getField() - .getName() - .equals(refOneToOne - .mappedBy())) { + if (this.clazz == nextRefMappedField.getField().getType() + && nextMappedField.getField().getName().equals(refOneToOne.mappedBy())) { // Found the referenced field. reverseMappedField = nextRefMappedField; break; @@ -1329,8 +1378,8 @@ public void initializeRelationships() { } } } - referencedMappedClass.nulledOnRemoveFieldReferences - .put(nextMappedField, reverseMappedField); + referencedMappedClass.addNulledOnRemoveFieldReferences + (nextMappedField, reverseMappedField); readAccessRightsFieldReferences.add(nextMappedField); } @@ -1339,14 +1388,12 @@ public void initializeRelationships() { // Check to see if this has any RelationshipInterfaces that need // to be addresed. - RelationshipInterface propInterface = (RelationshipInterface) nextMappedField - .getField().getAnnotation(RelationshipInterface.class); - if (propInterface != null) { - processMappedFieldRelationshipInterface(propInterface, + RelationshipInterface relationshipInterface = retrieveAnnotation(nextMappedField, RelationshipInterface.class); + if (relationshipInterface != null) { + processMappedFieldRelationshipInterface(relationshipInterface, nextMappedField); } - RelationshipInterfaces relationshipInterfaces = (RelationshipInterfaces) nextMappedField - .getField().getAnnotation(RelationshipInterfaces.class); + RelationshipInterfaces relationshipInterfaces = retrieveAnnotation(nextMappedField, RelationshipInterfaces.class); if (relationshipInterfaces != null) { for (RelationshipInterface nextPropInterface : relationshipInterfaces .relationshipInterfaces()) { @@ -1380,6 +1427,36 @@ public void initializeRelationships() { relationshipsInitialized = true; } + private void addCascadeRemoveFieldReferences(MappedField nextMappedField, MappedField reverseMappedField) { + cascadeRemoveFieldReferences.put(nextMappedField, reverseMappedField); + } + + public Map getNulledOnRemoveFieldReferences() { + Map result = new HashMap(this.nulledOnRemoveFieldReferences.size()); + MappedClass mappedClass = this; + while (mappedClass != null) { + result.putAll(mappedClass.nulledOnRemoveFieldReferences); + mappedClass = mappedClass.parentMappedClass; + } + + return result; + } + + public Map getCascadeRemoveFieldReferences() { + Map result = new HashMap(this.cascadeRemoveFieldReferences.size()); + MappedClass mappedClass = this; + while (mappedClass != null) { + result.putAll(mappedClass.cascadeRemoveFieldReferences); + mappedClass = mappedClass.parentMappedClass; + } + + return result; + } + + private void addNulledOnRemoveFieldReferences(MappedField nextMappedField, MappedField reverseMappedField) { + nulledOnRemoveFieldReferences.put(nextMappedField, reverseMappedField); + } + private void processMappedFieldRelationshipInterface( RelationshipInterface relInterface, MappedField mappedField) { if (relInterface == null @@ -1774,5 +1851,11 @@ public MappedClassMethodPair(MappedClass mappedClass, Method method) { this.method = method; } } - + + public IPerceroObject newPerceroObject() throws ClassNotFoundException, InstantiationException, IllegalAccessException { + IPerceroObject result = null; + result = (IPerceroObject) clazz.newInstance(); + return result; + } + } diff --git a/src/main/java/com/percero/agents/sync/metadata/MappedClassManager.java b/src/main/java/com/percero/agents/sync/metadata/MappedClassManager.java index 95598a0..79927eb 100644 --- a/src/main/java/com/percero/agents/sync/metadata/MappedClassManager.java +++ b/src/main/java/com/percero/agents/sync/metadata/MappedClassManager.java @@ -1,13 +1,13 @@ package com.percero.agents.sync.metadata; import java.util.Collection; -import java.util.HashMap; import java.util.Map; import java.util.Random; +import java.util.concurrent.ConcurrentHashMap; public class MappedClassManager implements IMappedClassManager { - private Map mappedClassesByName = new HashMap(); + private Map mappedClassesByName = new ConcurrentHashMap(); public void addMappedClass(MappedClass theMappedClass) { if (!mappedClassesByName.containsKey(theMappedClass.className)) diff --git a/src/main/java/com/percero/agents/sync/metadata/MappedField.java b/src/main/java/com/percero/agents/sync/metadata/MappedField.java index 726655b..b6671da 100644 --- a/src/main/java/com/percero/agents/sync/metadata/MappedField.java +++ b/src/main/java/com/percero/agents/sync/metadata/MappedField.java @@ -3,6 +3,7 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.percero.framework.metadata.IMappedQuery; +import com.percero.framework.vo.IPerceroObject; import java.io.IOException; import java.io.ObjectInput; @@ -26,7 +27,22 @@ public class MappedField { private Boolean hasReadAccessRights = false; protected MappedField reverseMappedField = null; + private boolean reverseMappedFieldValid = false; public MappedField getReverseMappedField() { + if (!reverseMappedFieldValid && reverseMappedField == null) { + MappedClass mc = this.getMappedClass(); + if (mc.parentMappedClass != null) { + MappedField mf = mc.parentMappedClass.getMappedFieldByName(this.getField().getName()); + + // If mf is null it means that this Mapped Field is NOT defined on the corresponding mapped class. + if (mf != null) { + MappedField reverseMappedField = mf.getReverseMappedField(); + this.reverseMappedField = reverseMappedField; + } + } + + reverseMappedFieldValid = true; + } return reverseMappedField; } @@ -209,4 +225,15 @@ else if (mfObj.getMappedClass() == null || this.getMappedClass() == null) return this.getMappedClass().equals(mfObj.getMappedClass()); } } + + public void setToNull(IPerceroObject perceroObject) throws IllegalArgumentException, IllegalAccessException { + boolean isAccessible = getField().isAccessible(); + if (!isAccessible) { + getField().setAccessible(true); + } + getField().set(perceroObject, null); + if (!isAccessible) { + getField().setAccessible(false); + } + } } diff --git a/src/main/java/com/percero/agents/sync/metadata/MappedFieldList.java b/src/main/java/com/percero/agents/sync/metadata/MappedFieldList.java index f4d314a..c0b2288 100644 --- a/src/main/java/com/percero/agents/sync/metadata/MappedFieldList.java +++ b/src/main/java/com/percero/agents/sync/metadata/MappedFieldList.java @@ -14,11 +14,14 @@ public class MappedFieldList extends MappedField { + @SuppressWarnings("rawtypes") private Class listClass = null; + @SuppressWarnings("rawtypes") public Class getListClass() { return listClass; } + @SuppressWarnings("rawtypes") public void setListClass(Class listClass) { this.listClass = listClass; } @@ -62,6 +65,7 @@ public Boolean isValueSetForQuery(Object anObject) throws IllegalArgumentExcepti return (value != null && !value.isEmpty()); } + @SuppressWarnings("rawtypes") public Boolean compareObjects(Object objectA, Object objectB) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { List valueA = (List) getValue(objectA); diff --git a/src/main/java/com/percero/agents/sync/services/DAODataProvider.java b/src/main/java/com/percero/agents/sync/services/DAODataProvider.java index 82464d8..fecdada 100644 --- a/src/main/java/com/percero/agents/sync/services/DAODataProvider.java +++ b/src/main/java/com/percero/agents/sync/services/DAODataProvider.java @@ -1,5 +1,6 @@ package com.percero.agents.sync.services; +import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; @@ -14,12 +15,9 @@ import java.util.UUID; import java.util.concurrent.TimeUnit; -import javassist.ClassPool; -import javassist.CtClass; -import javassist.CtField; -import javassist.CtMethod; - import org.apache.log4j.Logger; +import org.codehaus.jackson.JsonParseException; +import org.codehaus.jackson.map.JsonMappingException; import org.codehaus.jackson.map.ObjectMapper; import org.hibernate.PropertyValueException; import org.hibernate.Query; @@ -46,6 +44,12 @@ import com.percero.agents.sync.vo.IJsonObject; import com.percero.framework.vo.IPerceroObject; import com.percero.framework.vo.PerceroList; +import com.percero.serial.JsonUtils; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtField; +import javassist.CtMethod; @Component public class DAODataProvider implements IDataProvider { @@ -88,32 +92,43 @@ public void setDataProviderManager(IDataProviderManager value) { @SuppressWarnings({ "unchecked" }) - // TODO: @Transactional(readOnly=true) public PerceroList getAllByName(String className, Integer pageNumber, Integer pageSize, Boolean returnTotal, String userId) throws Exception { IDataAccessObject dao = (IDataAccessObject) DAORegistry.getInstance().getDataAccessObject(className); PerceroList results = dao.getAll(pageNumber, pageSize, returnTotal, userId, false); + List resultsToCache = new ArrayList(); if (results != null && !results.isEmpty()) { Iterator itrResults = results.iterator(); while (itrResults.hasNext()) { IPerceroObject nextResult = itrResults.next(); - try { - populateToManyRelationships(nextResult, true, null); - populateToOneRelationships(nextResult, true, null); - } catch (IllegalArgumentException e) { - e.printStackTrace(); - throw new SyncDataException(e); - } catch (IllegalAccessException e) { - e.printStackTrace(); - throw new SyncDataException(e); - } catch (InvocationTargetException e) { - e.printStackTrace(); - throw new SyncDataException(e); + + // If the object is in the cache, then we can use that instead of querying the database AGAIN for related objects. + IPerceroObject cachedResult = retrieveCachedObject(BaseDataObject.toClassIdPair(nextResult)); + if (cachedResult != null) { + results.set(results.indexOf(nextResult), cachedResult); + nextResult = cachedResult; + setObjectExpiration(nextResult); + } + else { + try { + populateToManyRelationships(nextResult, true, null); + populateToOneRelationships(nextResult, true, null); + resultsToCache.add(nextResult); + } catch (IllegalArgumentException e) { + throw new SyncDataException(e); + } catch (IllegalAccessException e) { + throw new SyncDataException(e); + } catch (InvocationTargetException e) { + throw new SyncDataException(e); + } } } } - putObjectsInRedisCache(results); + // We only need to put non-cached results into the cache. + if (!resultsToCache.isEmpty()) { + putObjectsInRedisCache(resultsToCache); + } // Now clean the objects for the user. List cleanedObjects = cleanObject(results, userId); @@ -141,7 +156,6 @@ public Set getAllClassIdPairsByName(String className) throws Except } @SuppressWarnings({ "unchecked" }) - // TODO: @Transactional(readOnly=true) public Integer countAllByName(String className, String userId) throws Exception { IDataAccessObject dao = (IDataAccessObject) DAORegistry.getInstance().getDataAccessObject(className); Integer result = dao.countAll(userId); @@ -236,26 +250,33 @@ protected static List processQueryResults(String resultClassName, Query public IPerceroObject findById(ClassIDPair classIdPair, String userId) { return findById(classIdPair, userId, false); } - @SuppressWarnings("unchecked") public IPerceroObject findById(ClassIDPair classIdPair, String userId, Boolean ignoreCache) { + return findById(classIdPair, userId, false, false); + } + @SuppressWarnings("unchecked") + public IPerceroObject findById(ClassIDPair classIdPair, String userId, Boolean ignoreCache, Boolean shellOnly) { try { IPerceroObject result = null; if (!ignoreCache) { - result = retrieveFromRedisCache(classIdPair); + result = retrieveFromRedisCache(classIdPair, shellOnly); } if (result == null) { IDataAccessObject dao = (IDataAccessObject) DAORegistry.getInstance().getDataAccessObject(classIdPair.getClassName()); // Retrieve results BEFORE applying access rules so that our cached value represents the full object. - result = dao.retrieveObject(classIdPair, null, false); + result = dao.retrieveObject(classIdPair, null, shellOnly); // Now put the object in the cache. if (result != null) { - populateToManyRelationships(result, true, null); - populateToOneRelationships(result, true, null); - putObjectInRedisCache(result); + if (!shellOnly) { + // Now need to populate relationships when only a shell object. + populateToManyRelationships(result, true, null); + populateToOneRelationships(result, true, null); + // We don't want to put a shell object in the cache. + putObjectInRedisCache(result, false); + } } else { // Not necessarily a problem but could be helpful when debugging. @@ -267,7 +288,9 @@ public IPerceroObject findById(ClassIDPair classIdPair, String userId, Boolean i setObjectExpiration(result); } - result = cleanObject(result, userId); + if (!shellOnly) { + result = cleanObject(result, userId); + } return result; } catch(Exception e) { @@ -278,15 +301,18 @@ public IPerceroObject findById(ClassIDPair classIdPair, String userId, Boolean i } - private void putObjectInRedisCache(IPerceroObject perceroObject) { + private void putObjectInRedisCache(IPerceroObject perceroObject, boolean onlyIfExists) { // Now put the object in the cache. if (cacheTimeout > 0 && perceroObject != null) { String key = RedisKeyUtils.classIdPair(perceroObject.getClass().getCanonicalName(), perceroObject.getID()); - cacheDataStore.setValue(key, ((BaseDataObject)perceroObject).toJson()); - setObjectExpiration(key); - String classKey = RedisKeyUtils.classIds(perceroObject.getClass().getCanonicalName()); - cacheDataStore.setSetValue(classKey, perceroObject.getID()); + if (!onlyIfExists || cacheDataStore.hasKey(key)) { + cacheDataStore.setValue(key, ((BaseDataObject)perceroObject).toJson()); + setObjectExpiration(key); + + String classKey = RedisKeyUtils.classIds(perceroObject.getClass().getCanonicalName()); + cacheDataStore.setSetValue(classKey, perceroObject.getID()); + } } } @@ -355,7 +381,7 @@ private void deleteObjectsFromRedisCache(List results) { } private void setObjectExpiration(IPerceroObject perceroObject) { - setObjectExpiration(RedisKeyUtils.classIdPair(perceroObject.getClass().getCanonicalName(), perceroObject.getID())); + setObjectExpiration(RedisKeyUtils.classIdPair(perceroObject.getClass().getCanonicalName(), perceroObject.getID())); } private void setObjectExpiration(String key) { @@ -367,11 +393,11 @@ private void setObjectExpiration(String key) { @Override public IPerceroObject retrieveCachedObject(ClassIDPair classIdPair) throws Exception { - return retrieveFromRedisCache(classIdPair); + return retrieveFromRedisCache(classIdPair, false); } @SuppressWarnings({ "rawtypes", "unchecked" }) - private IPerceroObject retrieveFromRedisCache(ClassIDPair classIdPair) throws Exception { + private IPerceroObject retrieveFromRedisCache(ClassIDPair classIdPair, boolean shellOnly) throws Exception { IPerceroObject result = null; if (cacheTimeout > 0) { IMappedClassManager mcm = MappedClassManagerFactory.getMappedClassManager(); @@ -379,29 +405,44 @@ private IPerceroObject retrieveFromRedisCache(ClassIDPair classIdPair) throws Ex MappedClass mc = mcm.getMappedClassByClassName(classIdPair.getClassName()); String key = RedisKeyUtils.classIdPair(classIdPair.getClassName(), classIdPair.getID()); - String jsonObjectString = (String) cacheDataStore.getValue(key); - if (jsonObjectString != null) { - if (IJsonObject.class.isAssignableFrom(theClass)) { - IJsonObject jsonObject = (IJsonObject) theClass.newInstance(); - jsonObject.fromJson(jsonObjectString); - result = (IPerceroObject) jsonObject; - } - else { - result = (IPerceroObject) safeObjectMapper.readValue(jsonObjectString, theClass); - } + // If we are only retrieving the shell object, then we really only care if the key exists. + if (shellOnly) { + if (cacheDataStore.hasKey(key)) { + result = (IPerceroObject) theClass.newInstance(); + result.setID(classIdPair.getID()); + } + else { + // Check MappedClass' child classes. + Iterator itrChildMappedClasses = mc.childMappedClasses.iterator(); + while (itrChildMappedClasses.hasNext()) { + MappedClass nextChildMc = itrChildMappedClasses.next(); + key = RedisKeyUtils.classIdPair(nextChildMc.className, classIdPair.getID()); + if (cacheDataStore.hasKey(key)) { + result = (IPerceroObject) nextChildMc.clazz.newInstance(); + result.setID(classIdPair.getID()); + break; + } + } + } } else { - // Check MappedClass' child classes. - Iterator itrChildMappedClasses = mc.childMappedClasses.iterator(); - while (itrChildMappedClasses.hasNext()) { - MappedClass nextChildMc = itrChildMappedClasses.next(); - key = RedisKeyUtils.classIdPair(nextChildMc.className, classIdPair.getID()); - jsonObjectString = (String) cacheDataStore.getValue(key); - if (jsonObjectString != null) { - result = (IPerceroObject) safeObjectMapper.readValue(jsonObjectString, theClass); - return result; - } - } + String jsonObjectString = (String) cacheDataStore.getValue(key); + if (jsonObjectString != null) { + result = createFromJson(jsonObjectString, theClass); + } + else { + // Check MappedClass' child classes. + Iterator itrChildMappedClasses = mc.childMappedClasses.iterator(); + while (itrChildMappedClasses.hasNext()) { + MappedClass nextChildMc = itrChildMappedClasses.next(); + key = RedisKeyUtils.classIdPair(nextChildMc.className, classIdPair.getID()); + jsonObjectString = (String) cacheDataStore.getValue(key); + if (jsonObjectString != null) { + result = createFromJson(jsonObjectString, nextChildMc.clazz); + break; + } + } + } } } @@ -410,6 +451,18 @@ private IPerceroObject retrieveFromRedisCache(ClassIDPair classIdPair) throws Ex } return result; } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + protected IPerceroObject createFromJson(String jsonObjectString, Class clazz) throws InstantiationException, IllegalAccessException, JsonParseException, JsonMappingException, IOException { + if (JsonUtils.isClassAssignableFromIJsonObject(clazz)) { + IJsonObject jsonObject = (IJsonObject) clazz.newInstance(); + jsonObject.fromJson(jsonObjectString); + return (IPerceroObject) jsonObject; + } + else { + return (IPerceroObject) safeObjectMapper.readValue(jsonObjectString, clazz); + } + } @SuppressWarnings({ "rawtypes", "unchecked" }) private Map retrieveFromRedisCache(ClassIDPairs classIdPairs, Boolean pleaseSetTimeout) throws Exception { @@ -417,15 +470,14 @@ private Map retrieveFromRedisCache(ClassIDPairs classIdP if (cacheTimeout > 0) { IMappedClassManager mcm = MappedClassManagerFactory.getMappedClassManager(); - Class theClass = MappedClass.forName(classIdPairs.getClassName()); MappedClass mc = mcm.getMappedClassByClassName(classIdPairs.getClassName()); - Set keys = new HashSet(classIdPairs.getIds().size()); + Map keys = new HashMap(classIdPairs.getIds().size()); Iterator itrIds = classIdPairs.getIds().iterator(); while (itrIds.hasNext()) { String nextId = itrIds.next(); String nextKey = RedisKeyUtils.classIdPair(classIdPairs.getClassName(), nextId); - keys.add(nextKey); + keys.put(nextKey, mc.clazz); // Check MappedClass' child classes. Iterator itrChildMappedClasses = mc.childMappedClasses.iterator(); @@ -436,51 +488,21 @@ private Map retrieveFromRedisCache(ClassIDPairs classIdP break; } nextKey = RedisKeyUtils.classIdPair(nextChildMc.className, nextId); - keys.add(nextKey); + keys.put(nextKey, nextChildMc.clazz); } } - -// String key = RedisKeyUtils.classIdPair(classIdPair.getClassName(), classIdPair.getID()); - List jsonObjectStrings = cacheDataStore.getValues(keys); - Iterator itrJsonObjectStrings = jsonObjectStrings.iterator(); - while (itrJsonObjectStrings.hasNext()) { - String jsonObjectString = (String) itrJsonObjectStrings.next(); + + for(Entry nextKeyClass : keys.entrySet()) { + String jsonObjectString = (String) cacheDataStore.getValue(nextKeyClass.getKey()); if (jsonObjectString != null) { - if (IJsonObject.class.isAssignableFrom(theClass)) { - IJsonObject jsonObject = (IJsonObject) theClass.newInstance(); - jsonObject.fromJson(jsonObjectString); - if (jsonObject instanceof BaseDataObject) { - ((BaseDataObject) jsonObject).setDataSource(BaseDataObject.DATA_SOURCE_CACHE); - } - result.put( (((IPerceroObject) jsonObject).getID()), (IPerceroObject) jsonObject ); - } - else { - IPerceroObject nextPerceroObject = (IPerceroObject) safeObjectMapper.readValue(jsonObjectString, theClass); - - if (nextPerceroObject instanceof BaseDataObject) { - ((BaseDataObject) nextPerceroObject).setDataSource(BaseDataObject.DATA_SOURCE_CACHE); - } - result.put( nextPerceroObject.getID(), nextPerceroObject); - } - + IPerceroObject nextPerceroObject = createFromJson(jsonObjectString, nextKeyClass.getValue()); + ((BaseDataObject) nextPerceroObject).setDataSource(BaseDataObject.DATA_SOURCE_CACHE); + result.put(nextPerceroObject.getID(), nextPerceroObject); } -// else { -// // Check MappedClass' child classes. -// Iterator itrChildMappedClasses = mc.childMappedClasses.iterator(); -// while (itrChildMappedClasses.hasNext()) { -// MappedClass nextChildMc = itrChildMappedClasses.next(); -// key = RedisKeyUtils.classIdPair(nextChildMc.className, classIdPair.getID()); -// jsonObjectString = (String) redisDataStore.getValue(key); -// if (jsonObjectString != null) { -// result = (IPerceroObject) safeObjectMapper.readValue(jsonObjectString, theClass); -// return result; -// } -// } -// } } if (pleaseSetTimeout) { - cacheDataStore.expire(keys, cacheTimeout, TimeUnit.SECONDS); + cacheDataStore.expire(keys.keySet(), cacheTimeout, TimeUnit.SECONDS); } } @@ -552,7 +574,6 @@ public List findByIds(ClassIDPairs classIdPairs, String userId, } catch(Exception e) { log.error(e); - e.printStackTrace(); } return results; @@ -563,31 +584,49 @@ public List findByIds(ClassIDPairs classIdPairs, String userId, public List findByExample(IPerceroObject theQueryObject, List excludeProperties, String userId, Boolean shellOnly) throws SyncException { IDataAccessObject dao = (IDataAccessObject) DAORegistry.getInstance().getDataAccessObject(theQueryObject.getClass().getCanonicalName()); List results = dao.findByExample(theQueryObject, excludeProperties, userId, shellOnly); + List resultsToCache = new ArrayList(); if (results != null && !results.isEmpty()) { Iterator itrResults = results.iterator(); while (itrResults.hasNext()) { IPerceroObject nextResult = itrResults.next(); - try { - populateToManyRelationships(nextResult, true, null); - populateToOneRelationships(nextResult, true, null); - } catch (IllegalArgumentException e) { - e.printStackTrace(); - throw new SyncDataException(e); - } catch (IllegalAccessException e) { - e.printStackTrace(); - throw new SyncDataException(e); - } catch (InvocationTargetException e) { - e.printStackTrace(); - throw new SyncDataException(e); - } + + if (!shellOnly) { + try { + // If the object is in the cache, then we can use that instead of querying the database AGAIN for related objects. + IPerceroObject cachedResult = retrieveCachedObject(BaseDataObject.toClassIdPair(nextResult)); + if (cachedResult != null) { + results.set(results.indexOf(nextResult), cachedResult); + nextResult = cachedResult; + setObjectExpiration(nextResult); + } + else { + populateToManyRelationships(nextResult, true, null); + populateToOneRelationships(nextResult, true, null); + + resultsToCache.add(nextResult); + } + } catch (IllegalArgumentException e) { + throw new SyncDataException(e); + } catch (IllegalAccessException e) { + throw new SyncDataException(e); + } catch (InvocationTargetException e) { + throw new SyncDataException(e); + } catch (Exception e) { + throw new SyncDataException(e); + } + } } } - putObjectsInRedisCache(results); - - // Now clean the objects for the user. - results = cleanObject(results, userId); + if (!shellOnly) { + if (!resultsToCache.isEmpty()) { + putObjectsInRedisCache(resultsToCache); + } + + // Now clean the objects for the user. + results = cleanObject(results, userId); + } return results; } @@ -605,82 +644,53 @@ public T createObject(T perceroObject, String userId) else { // Check to see if item already exists. try { - IPerceroObject existingObject = dao.retrieveObject(BaseDataObject.toClassIdPair(perceroObject), null, false); - if (existingObject != null) - { - populateToManyRelationships(perceroObject, true, null); - populateToOneRelationships(perceroObject, true, null); - return (T) cleanObject(perceroObject, userId); + // We want to see if this object already exists, we will + // check the cache first, then the database. + // We search without a UserID in the case that the object + // exists, but the user does NOT have access to + // it. Then we clean the object after retrieval (if it has + // been found) + IPerceroObject existingObject = findById(BaseDataObject.toClassIdPair(perceroObject), null); + if (existingObject != null) { + return (T) cleanObject(existingObject, userId); } } catch( Exception e) { log.debug("Error retrieving object on create", e); } } - - perceroObject = (T) dao.createObject(perceroObject, userId); - if (perceroObject == null) { - return perceroObject; + + // The incoming perceroObject will have all of it's source + // relationships filled (by definition, it can't have any target + // relationships yet since it is a new object). + + IPerceroObject createdPerceroObject = (T) dao.createObject(perceroObject, userId); + if (createdPerceroObject == null) { + // User must not have permission to create the object. + return null; } - populateToManyRelationships(perceroObject, true, null); - populateToOneRelationships(perceroObject, true, null); + + // Reset all the relationships in the case that dao.createObject removed them from the object. + overwriteToManyRelationships(createdPerceroObject, perceroObject); + overwriteToOneRelationships(createdPerceroObject, perceroObject); // Now update the cache. - // TODO: Field-level updates could be REALLY useful here. Would avoid A TON of UNNECESSARY work... if (cacheTimeout > 0) { - String key = RedisKeyUtils.classIdPair(perceroObject.getClass().getCanonicalName(), perceroObject.getID()); - cacheDataStore.setValue(key, ((BaseDataObject)perceroObject).toJson()); - cacheDataStore.expire(key, cacheTimeout, TimeUnit.SECONDS); - - Set keysToDelete = new HashSet(); - MappedClass nextMappedClass = mcm.getMappedClassByClassName(perceroObject.getClass().getName()); - Iterator itrToManyFields = nextMappedClass.toManyFields.iterator(); - while(itrToManyFields.hasNext()) { - MappedField nextMappedField = itrToManyFields.next(); - Object fieldObject = nextMappedField.getGetter().invoke(perceroObject); - if (fieldObject != null) { - if (fieldObject instanceof IPerceroObject) { - String nextKey = RedisKeyUtils.classIdPair(fieldObject.getClass().getCanonicalName(), ((IPerceroObject)fieldObject).getID()); - keysToDelete.add(nextKey); - } - else if (fieldObject instanceof Collection) { - Iterator itrFieldObject = ((Collection) fieldObject).iterator(); - while(itrFieldObject.hasNext()) { - Object nextListObject = itrFieldObject.next(); - if (nextListObject instanceof IPerceroObject) { - String nextKey = RedisKeyUtils.classIdPair(nextListObject.getClass().getCanonicalName(), ((IPerceroObject)nextListObject).getID()); - keysToDelete.add(nextKey); - } - } - } - } - } - Iterator itrToOneFields = mappedClass.toOneFields.iterator(); - while(itrToOneFields.hasNext()) { - MappedFieldPerceroObject nextMappedField = itrToOneFields.next(); - Object fieldObject = nextMappedField.getGetter().invoke(perceroObject); - if (fieldObject != null) { - if (fieldObject instanceof IPerceroObject) { - String nextKey = RedisKeyUtils.classIdPair(fieldObject.getClass().getCanonicalName(), ((IPerceroObject)fieldObject).getID()); - keysToDelete.add(nextKey); - } - else if (fieldObject instanceof Collection) { - Iterator itrFieldObject = ((Collection) fieldObject).iterator(); - while(itrFieldObject.hasNext()) { - Object nextListObject = itrFieldObject.next(); - if (nextListObject instanceof IPerceroObject) { - String nextKey = RedisKeyUtils.classIdPair(nextListObject.getClass().getCanonicalName(), ((IPerceroObject)nextListObject).getID()); - keysToDelete.add(nextKey); - } - } - } - } - } - - if (!keysToDelete.isEmpty()) { - cacheDataStore.deleteKeys(keysToDelete); - // TODO: Do we simply delete the key? Or do we refetch the object here and update the key? - //redisDataStore.setValue(nextKey, ((BaseDataObject)perceroObject).toJson()); - } + putObjectInRedisCache(createdPerceroObject, false); + + // For each source related object, we need to handle the update + // by updating the cache. We only care about source mapped fields + // because those are the only ones that are possible to be present + // here since this is a new object. + for(MappedFieldPerceroObject nextSourceMappedField : mappedClass.getSourceMappedFields()) { + if (nextSourceMappedField.getReverseMappedField() != null) { + IPerceroObject relatedObject = (IPerceroObject) nextSourceMappedField.getValue(createdPerceroObject); + if (relatedObject != null) { + Collection reverseMappedFields = new ArrayList(1); + reverseMappedFields.add(nextSourceMappedField.getReverseMappedField()); + handleUpdatedClassIdPair(createdPerceroObject, BaseDataObject.toClassIdPair(relatedObject), reverseMappedFields, userId); + } + } + } } return (T) cleanObject(perceroObject, userId); @@ -707,52 +717,51 @@ else if (fieldObject instanceof Collection) { @SuppressWarnings("unchecked") public T putObject(T perceroObject, Map> changedFields, String userId) throws SyncException { IDataAccessObject dao = (IDataAccessObject) DAORegistry.getInstance().getDataAccessObject(perceroObject.getClass().getCanonicalName()); - perceroObject = (T) dao.updateObject(perceroObject, changedFields, userId); + IPerceroObject updateResultObject = (T) dao.updateObject(perceroObject, changedFields, userId); + + if (updateResultObject == null) { + // The update failed, so we can return here. + return null; + } try { - populateToManyRelationships(perceroObject, true, null); - populateToOneRelationships(perceroObject, true, null); + // Overwrite the related objects. No need to go to the database since these could NOT have changed in this update. + overwriteToManyRelationships(updateResultObject, perceroObject); + overwriteToOneRelationships(updateResultObject, perceroObject); } catch (IllegalArgumentException e) { - e.printStackTrace(); - throw new SyncException(e); + throw new SyncDataException(SyncDataException.UPDATE_OBJECT_ERROR, SyncDataException.UPDATE_OBJECT_ERROR_CODE, e); } catch (IllegalAccessException e) { - e.printStackTrace(); - throw new SyncException(e); + throw new SyncException(SyncDataException.UPDATE_OBJECT_ERROR, SyncDataException.UPDATE_OBJECT_ERROR_CODE, e); } catch (InvocationTargetException e) { - e.printStackTrace(); - throw new SyncException(e); + throw new SyncException(SyncDataException.UPDATE_OBJECT_ERROR, SyncDataException.UPDATE_OBJECT_ERROR_CODE, e); } // Now update the cache. if (cacheTimeout > 0) { // TODO: Also need to update the caches of anything object that is related to this object. - String key = RedisKeyUtils.classIdPair(perceroObject.getClass().getCanonicalName(), perceroObject.getID()); - if (cacheDataStore.hasKey(key)) { - cacheDataStore.setValue(key, ((BaseDataObject)perceroObject).toJson()); - } + putObjectInRedisCache(updateResultObject, true); - List pairsToDelete = new ArrayList(); + List cachePairsToUpdate = new ArrayList(); // Iterate through each changed object and reset the cache for that object. if (changedFields != null) { - Iterator itrChangedFieldKeyset = changedFields.keySet().iterator(); - while (itrChangedFieldKeyset.hasNext()) { - ClassIDPair thePair = itrChangedFieldKeyset.next(); - if (!thePair.comparePerceroObject(perceroObject)) { - pairsToDelete.add(thePair); -// String nextKey = RedisKeyUtils.classIdPair(thePair.getClassName(), thePair.getID()); -// pairsToDelete.add(nextKey); - } - } + Iterator>> itrChangedFieldEntrySet = changedFields.entrySet().iterator(); + while (itrChangedFieldEntrySet.hasNext()) { + Entry> nextEntry = itrChangedFieldEntrySet.next(); + ClassIDPair thePair = nextEntry.getKey(); + Collection mappedFields = nextEntry.getValue(); + handleUpdatedClassIdPair(updateResultObject, thePair, mappedFields, userId); + } } else { + log.error("No Changed fields when updating object " + perceroObject.getClass().getCanonicalName() + "::" + perceroObject.getID()); // No changedFields? We should never get here? IMappedClassManager mcm = MappedClassManagerFactory.getMappedClassManager(); - MappedClass mappedClass = mcm.getMappedClassByClassName(perceroObject.getClass().getName()); + MappedClass mappedClass = mcm.getMappedClassByClassName(updateResultObject.getClass().getName()); Iterator itrToManyFields = mappedClass.toManyFields.iterator(); while(itrToManyFields.hasNext()) { MappedField nextMappedField = itrToManyFields.next(); Object fieldObject = null; try { - fieldObject = nextMappedField.getGetter().invoke(perceroObject); + fieldObject = nextMappedField.getGetter().invoke(updateResultObject); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { @@ -762,7 +771,7 @@ public T putObject(T perceroObject, Map void handleUpdatedClassIdPair(T originalUpdatedObject, ClassIDPair theRelatedObjectClassIdPair, + Collection mappedFields, String userId) { + + // Make sure that theRelatedClassIdPair is not the same as the original object + if (!theRelatedObjectClassIdPair.comparePerceroObject(originalUpdatedObject)) { + // This should contain at least one field, otherwise nothing as changed (most likely a one-way relationship). + if (mappedFields != null && !mappedFields.isEmpty()) { + IPerceroObject nextUpdatedCachedObject; + try { + nextUpdatedCachedObject = retrieveCachedObject(theRelatedObjectClassIdPair); + boolean cachedObjectUpdated = false; + + // If the related object is NOT in the cache, then we having nothing to do. + if (nextUpdatedCachedObject != null) { + for(MappedField nextMappedField : mappedFields) { + try { + if (nextMappedField.getMappedClass().toManyFields.contains(nextMappedField)) { + if (handleUpdatedClassIdPair_ToManyMappedField( + originalUpdatedObject, nextUpdatedCachedObject, nextMappedField, userId)) { + cachedObjectUpdated = true; + } + } + else if (nextMappedField.getMappedClass().toOneFields.contains(nextMappedField)) { + if (handleUpdatedClassIdPair_ToOneMappedField( + originalUpdatedObject, nextUpdatedCachedObject, nextMappedField, userId)) { + cachedObjectUpdated = true; + } + } + } catch(Exception e) { + log.error("Unable to retrieve related objects for " + originalUpdatedObject.getClass().getCanonicalName() + "::" + nextMappedField.getField().getName()); + } + } + + if (cachedObjectUpdated) { + // Update the cached object. + putObjectInRedisCache(nextUpdatedCachedObject, false); + } + } + } catch (Exception e) { + log.error("Error updating related object " + theRelatedObjectClassIdPair.toJson(), e); + } + } + } + } + + /** + * @param originalUpdatedObject + * @param currentRelatedObject + * @param relatedMappedField + * @param userId + * @return + * @throws IllegalAccessException + * @throws InvocationTargetException + * @throws SyncException + */ + @SuppressWarnings("unchecked") + private boolean handleUpdatedClassIdPair_ToManyMappedField(T originalUpdatedObject, + IPerceroObject currentRelatedObject, MappedField relatedMappedField, String userId) + throws IllegalAccessException, InvocationTargetException, SyncException { + boolean cachedObjectUpdated = false; + + IPerceroObject newRelatedObject = (IPerceroObject) (relatedMappedField.getReverseMappedField() != null ? relatedMappedField.getReverseMappedField().getGetter().invoke(originalUpdatedObject) : null); + if (newRelatedObject != null) { + List updatedList = (List) relatedMappedField.getGetter().invoke(currentRelatedObject); + if (updatedList == null) { + updatedList = new ArrayList(); + relatedMappedField.getSetter().invoke(currentRelatedObject, updatedList); + } + + // Find the index of the original object in this related mapped list, if it exists. + int collectionIndex = findPerceroObjectInList(originalUpdatedObject, + updatedList); + + if (BaseDataObject.toClassIdPair(currentRelatedObject).comparePerceroObject(newRelatedObject)) { + // The perceroObject has been ADDED to the list. + if (collectionIndex < 0) { + // Only add the perceroObject to the Collection if it is NOT already there. + updatedList.add(originalUpdatedObject); + cachedObjectUpdated = true; + } + } + else { + // The perceroObject has been REMOVED from the list. + if (collectionIndex >= 0){ + updatedList.remove(collectionIndex); + cachedObjectUpdated = true; + } + } + } + else { + // We are unable to get a hold of the reverse mapped field, so we default back to the underlying data store. + List allRelatedObjects = findAllRelatedObjects(currentRelatedObject, relatedMappedField, true, userId); + relatedMappedField.getSetter().invoke(currentRelatedObject, allRelatedObjects); + cachedObjectUpdated = true; + } + return cachedObjectUpdated; + } + + /** + * @param originalUpdatedObject + * @param currentRelatedObject + * @param relatedMappedField + * @param userId + * @return + * @throws IllegalAccessException + * @throws InvocationTargetException + * @throws SyncException + */ + private boolean handleUpdatedClassIdPair_ToOneMappedField(T originalUpdatedObject, + IPerceroObject currentRelatedObject, MappedField relatedMappedField, String userId) + throws IllegalAccessException, InvocationTargetException, SyncException { + + boolean cachedObjectUpdated = false; + + IPerceroObject newRelatedObject = (IPerceroObject) (relatedMappedField.getReverseMappedField() != null ? relatedMappedField.getReverseMappedField().getGetter().invoke(originalUpdatedObject) : null); + if (newRelatedObject != null) { + IPerceroObject oldUpdatedObject = (IPerceroObject) relatedMappedField.getGetter().invoke(currentRelatedObject); + + if (BaseDataObject.toClassIdPair(currentRelatedObject).comparePerceroObject(newRelatedObject)) { + // If the newRelatedPerceroObject is the same as the + // currentRelatedPerceroObject, then we know that this is the NEW + // related object. + + // Only add the perceroObject to the Collection if it is NOT already there. + if (!BaseDataObject.toClassIdPair(originalUpdatedObject).comparePerceroObject(oldUpdatedObject)) { +// oldRelatedObject.add(originalUpdatedObject); + relatedMappedField.getSetter().invoke(currentRelatedObject, originalUpdatedObject); + cachedObjectUpdated = true; + } + } + else { + // Else, we know that this is the OLD related object. + // The perceroObject has been REMOVED from the list. + if (oldUpdatedObject != null) { +// oldRelatedObject.remove(collectionIndex); + boolean isAccessible = relatedMappedField.getField().isAccessible(); + if (!isAccessible) { + relatedMappedField.getField().setAccessible(true); + } + relatedMappedField.getField().set(currentRelatedObject, null); + if (!isAccessible) { + relatedMappedField.getField().setAccessible(false); + } + cachedObjectUpdated = true; + } + } + } + else { + // We are unable to get a hold of the reverse mapped field, so we default back to the underlying data store. + // We need to go to the dataProvider for the related object's class and ask for it. + // Though this returns a List, we expect there to be only one result in the list. + List allRelatedObjects = findAllRelatedObjects(currentRelatedObject, relatedMappedField, true, userId); + IPerceroObject relatedPerceroObject = null; + if (allRelatedObjects != null && !allRelatedObjects.isEmpty()) { + relatedPerceroObject = allRelatedObjects.get(0); + } + relatedMappedField.getSetter().invoke(currentRelatedObject, relatedPerceroObject); + cachedObjectUpdated = true; + } + return cachedObjectUpdated; +// +// boolean cachedObjectUpdated; +// // We need to go to the dataProvider for the related object's class and ask for it. +// // Though this returns a List, we expect there to be only one result in the list. +// List allRelatedObjects = findAllRelatedObjects(currentRelatedObject, relatedMappedField, true, userId); +// IPerceroObject relatedPerceroObject = null; +// if (allRelatedObjects != null && !allRelatedObjects.isEmpty()) { +// relatedPerceroObject = allRelatedObjects.get(0); +// } +// relatedMappedField.getSetter().invoke(currentRelatedObject, relatedPerceroObject); +// cachedObjectUpdated = true; +// return cachedObjectUpdated; + } + + /** + * @param originalUpdatedObject + * @param updatedList + * @return + */ + protected int findPerceroObjectInList(T originalUpdatedObject, + List updatedList) { + int index = 0; + Iterator itrUpdatedCollection = updatedList.iterator(); + ClassIDPair perceroIdPair = BaseDataObject.toClassIdPair(originalUpdatedObject); + + while (itrUpdatedCollection.hasNext()) { + IPerceroObject nextCollectionObject = itrUpdatedCollection.next(); + if (perceroIdPair.comparePerceroObject(nextCollectionObject)) { + return index; + } + index++; + } + + return -1; + } + //////////////////////////////////////////////////// // DELETE //////////////////////////////////////////////////// - @SuppressWarnings({ "unchecked", "rawtypes" }) + @SuppressWarnings({ "unchecked" }) public Boolean deleteObject(ClassIDPair theClassIdPair, String userId) throws SyncException { if (theClassIdPair == null || !StringUtils.hasText(theClassIdPair.getID())) { @@ -853,96 +1076,111 @@ public Boolean deleteObject(ClassIDPair theClassIdPair, String userId) throws Sy } IDataAccessObject dao = (IDataAccessObject) DAORegistry.getInstance().getDataAccessObject(theClassIdPair.getClassName()); - IPerceroObject perceroObject = dao.retrieveObject(theClassIdPair, null, false); // Retrieve the full object so we can update the cache if the delete is successful. - Boolean result = dao.deleteObject(theClassIdPair, userId); + IPerceroObject originalDeletedObject = dao.retrieveObject(theClassIdPair, null, false); // Retrieve the full object so we can update the cache if the delete is successful. - if (perceroObject == null) { + if (originalDeletedObject == null) { + // If the object already does NOT exist, then there is nothing to do. return true; } + + Boolean result = dao.deleteObject(theClassIdPair, userId); try { - MappedClass mappedClass = MappedClassManagerFactory.getMappedClassManager().getMappedClassByClassName(perceroObject.getClass().getCanonicalName()); + MappedClass mappedClass = MappedClassManagerFactory.getMappedClassManager().getMappedClassByClassName(originalDeletedObject.getClass().getCanonicalName()); if (mappedClass == null) { - log.warn("Missing MappedClass for " + perceroObject.getClass().getCanonicalName()); - throw new SyncException(SyncException.MISSING_MAPPED_CLASS_ERROR, SyncException.MISSING_MAPPED_CLASS_ERROR_CODE ); + log.warn("Missing MappedClass for " + originalDeletedObject.getClass().getCanonicalName()); + throw new SyncException(SyncException.MISSING_MAPPED_CLASS_ERROR, SyncException.MISSING_MAPPED_CLASS_ERROR_CODE); } // Now delete from cache. // Now update the cache. // TODO: Field-level updates could be REALLY useful here. Would avoid A TON of UNNECESSARY work... if (result && cacheTimeout > 0) { - List objectsToDelete = new ArrayList(); - -// String key = RedisKeyUtils.classIdPair(perceroObject.getClass().getCanonicalName(), perceroObject.getID()); - objectsToDelete.add(BaseDataObject.toClassIdPair(perceroObject)); - Iterator itrToManyFields = mappedClass.toManyFields.iterator(); while(itrToManyFields.hasNext()) { - MappedField nextMappedField = itrToManyFields.next(); - Object fieldObject = nextMappedField.getGetter().invoke(perceroObject); - if (fieldObject != null) { - if (fieldObject instanceof IPerceroObject) { - objectsToDelete.add(BaseDataObject.toClassIdPair((IPerceroObject)fieldObject)); -// String nextKey = RedisKeyUtils.classIdPair(fieldObject.getClass().getCanonicalName(), ((IPerceroObject)fieldObject).getID()); -// objectsToDelete.add(nextKey); - } - else if (fieldObject instanceof Collection) { - Iterator itrFieldObject = ((Collection) fieldObject).iterator(); - while(itrFieldObject.hasNext()) { - Object nextListObject = itrFieldObject.next(); - if (nextListObject instanceof IPerceroObject) { - objectsToDelete.add(BaseDataObject.toClassIdPair((IPerceroObject) nextListObject)); -// String nextKey = RedisKeyUtils.classIdPair(nextListObject.getClass().getCanonicalName(), ((IPerceroObject)nextListObject).getID()); -// objectsToDelete.add(nextKey); - } - } - } - } + handleDeletedClassIDPair_MappedField(originalDeletedObject, itrToManyFields.next()); } Iterator itrToOneFields = mappedClass.toOneFields.iterator(); while(itrToOneFields.hasNext()) { - MappedFieldPerceroObject nextMappedField = itrToOneFields.next(); - Object fieldObject = nextMappedField.getGetter().invoke(perceroObject); - if (fieldObject != null) { - if (fieldObject instanceof IPerceroObject) { - objectsToDelete.add(BaseDataObject.toClassIdPair((IPerceroObject) fieldObject)); -// String nextKey = RedisKeyUtils.classIdPair(fieldObject.getClass().getCanonicalName(), ((IPerceroObject)fieldObject).getID()); -// objectsToDelete.add(nextKey); - } - else if (fieldObject instanceof Collection) { - Iterator itrFieldObject = ((Collection) fieldObject).iterator(); - while(itrFieldObject.hasNext()) { - Object nextListObject = itrFieldObject.next(); - if (nextListObject instanceof IPerceroObject) { - objectsToDelete.add(BaseDataObject.toClassIdPair((IPerceroObject) nextListObject)); -// String nextKey = RedisKeyUtils.classIdPair(nextListObject.getClass().getCanonicalName(), ((IPerceroObject)nextListObject).getID()); -// objectsToDelete.add(nextKey); - } - } - } - } + handleDeletedClassIDPair_MappedField(originalDeletedObject, itrToOneFields.next()); } - if (!objectsToDelete.isEmpty()) { - deleteObjectsFromRedisCache(objectsToDelete); -// cacheDataStore.deleteKeys(objectsToDelete); - } + deleteObjectFromRedisCache(BaseDataObject.toClassIdPair(originalDeletedObject)); } } catch(Exception e) { - if (perceroObject != null) { - log.error("Unable to delete record from database: " + perceroObject.getClass().getCanonicalName() + ":" + perceroObject.getID(), e); - } - else { - log.error("Unable to delete record from database: NULL Object", e); - } - throw new SyncDataException(SyncDataException.DELETE_OBJECT_ERROR, SyncDataException.DELETE_OBJECT_ERROR_CODE); + throw new SyncDataException(SyncDataException.DELETE_OBJECT_ERROR, SyncDataException.DELETE_OBJECT_ERROR_CODE, e); } return result; } + /** + * @param originalDeletedObject + * @param itrToOneFields + * @return + * @throws Exception + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + private boolean handleDeletedClassIDPair_MappedField(IPerceroObject originalDeletedObject, + MappedField mappedField) + throws Exception { + + if (mappedField == null) { + return false; + } + if (mappedField.getReverseMappedField() == null) { + // Nothing to do since the related object does not know about the relationship. + return true; + } + + Object fieldObject = mappedField.getGetter().invoke(originalDeletedObject); + + // We only need to update this related object(s) if it is set. + if (fieldObject != null) { + if (fieldObject instanceof IPerceroObject) { + fieldObject = retrieveCachedObject(BaseDataObject.toClassIdPair((IPerceroObject) fieldObject)); + Object theRelatedObject = mappedField.getReverseMappedField().getGetter().invoke(fieldObject); + if (theRelatedObject != null) { + if (theRelatedObject instanceof IPerceroObject) { + // Since the value is set, un-set it and save back to to the data store. + mappedField.getReverseMappedField().setToNull((IPerceroObject) fieldObject); + putObjectInRedisCache((IPerceroObject) fieldObject, true); + } + else if (theRelatedObject instanceof List) { + int i = 0; + for(IPerceroObject nextRelatedObject : (List) theRelatedObject) { + if (BaseDataObject.toClassIdPair(originalDeletedObject).comparePerceroObject(nextRelatedObject)) { + ((List) theRelatedObject).remove(i); + break; + } + i++; + } + putObjectInRedisCache((IPerceroObject) fieldObject, true); + } + } + } + else if (fieldObject instanceof Collection) { + Iterator itrFieldObject = ((Collection) fieldObject).iterator(); + while(itrFieldObject.hasNext()) { + Object nextListObject = itrFieldObject.next(); + if (nextListObject instanceof IPerceroObject) { + nextListObject = retrieveCachedObject(BaseDataObject.toClassIdPair((IPerceroObject) nextListObject)); + IPerceroObject theRelatedObject = (IPerceroObject) mappedField.getReverseMappedField().getGetter().invoke(nextListObject); + if (theRelatedObject != null) { + // Since the value is set, un-set it and save back to to the data store. + mappedField.getReverseMappedField().setToNull((IPerceroObject) nextListObject); + putObjectInRedisCache((IPerceroObject) nextListObject, true); + } + } + } + } + } + return true; + } + + //////////////////////////////////////////////////// // CLEAN //////////////////////////////////////////////////// @@ -1286,6 +1524,24 @@ public void populateToManyRelationships(IPerceroObject perceroObject, Boolean sh } } } + + protected void overwriteToManyRelationships(IPerceroObject perceroObject, IPerceroObject sourceObject) { + if (perceroObject == null || sourceObject == null) { + // Invalid object. + log.warn("Invalid object in overwriteToManyRelationships"); + return; + } + + MappedClass mappedClass = MappedClassManagerFactory.getMappedClassManager().getMappedClassByClassName(perceroObject.getClass().getCanonicalName()); + for(MappedField nextToManyMappedField : mappedClass.toManyFields) { + + try { + nextToManyMappedField.getSetter().invoke(perceroObject, nextToManyMappedField.getGetter().invoke(sourceObject)); + } catch(Exception e) { + log.error("Unable to retrieve related objects for " + perceroObject.getClass().getCanonicalName() + "::" + nextToManyMappedField.getField().getName()); + } + } + } public void populateToOneRelationships(IPerceroObject perceroObject, Boolean shellOnly, String userId) throws SyncException, IllegalArgumentException, @@ -1361,16 +1617,22 @@ public void populateToOneRelationships(IPerceroObject perceroObject, Boolean she } } } - -// protected void addObjectToCache(IPerceroObject nextPerceroObject) { -// String key = RedisKeyUtils.classIdPair(nextPerceroObject.getClass().getCanonicalName(), nextPerceroObject.getID()); -// if (cacheTimeout > 0) -// cacheDataStore.setValue(key, ((BaseDataObject)nextPerceroObject).toJson()); -// -// // (Re)Set the expiration. -// if (cacheTimeout > 0 && key != null) { -// cacheDataStore.expire(key, cacheTimeout, TimeUnit.SECONDS); -// } -// } + + protected void overwriteToOneRelationships(IPerceroObject perceroObject, IPerceroObject sourceObject) throws SyncException, IllegalArgumentException, + IllegalAccessException, InvocationTargetException { + if (perceroObject == null || sourceObject == null) { + // Invalid object. + log.warn("Invalid object in overwriteToOneRelationships"); + return; + } + + MappedClass mappedClass = MappedClassManagerFactory.getMappedClassManager().getMappedClassByClassName(perceroObject.getClass().getCanonicalName()); + for(MappedFieldPerceroObject nextToOneMappedField : mappedClass.toOneFields) { + // If perceroObject is the "owner" of this relationship, then we have all the data necessary here. + if (!nextToOneMappedField.isSourceEntity()) { + nextToOneMappedField.getSetter().invoke(perceroObject, nextToOneMappedField.getGetter().invoke(sourceObject)); + } + } + } } diff --git a/src/main/java/com/percero/agents/sync/services/IDataProvider.java b/src/main/java/com/percero/agents/sync/services/IDataProvider.java index ace4c59..3131032 100644 --- a/src/main/java/com/percero/agents/sync/services/IDataProvider.java +++ b/src/main/java/com/percero/agents/sync/services/IDataProvider.java @@ -25,6 +25,7 @@ public interface IDataProvider { public List runQuery(MappedClass mappedClass, String queryName, Object[] queryArguments, String clientId) throws SyncException; public IPerceroObject findById(ClassIDPair classIdPair, String userId); public IPerceroObject findById(ClassIDPair classIdPair, String userId, Boolean ignoreCache); + public IPerceroObject findById(ClassIDPair classIdPair, String userId, Boolean ignoreCache, Boolean shellOnly); public IPerceroObject retrieveCachedObject(ClassIDPair classIdPair) throws Exception; public List findByIds(ClassIDPairs classIdPairs, String userId); public List findByIds(ClassIDPairs classIdPairs, String userId, Boolean ignoreCache); diff --git a/src/main/java/com/percero/agents/sync/services/IPushSyncHelper.java b/src/main/java/com/percero/agents/sync/services/IPushSyncHelper.java index bf365aa..0565215 100644 --- a/src/main/java/com/percero/agents/sync/services/IPushSyncHelper.java +++ b/src/main/java/com/percero/agents/sync/services/IPushSyncHelper.java @@ -2,7 +2,9 @@ import java.util.Collection; +import com.percero.agents.sync.vo.ClassIDPair; import com.percero.agents.sync.vo.SyncResponse; +import com.percero.framework.vo.IPerceroObject; public interface IPushSyncHelper { @@ -17,4 +19,7 @@ public interface IPushSyncHelper { public Boolean removeClient(String clientId); public Boolean renameClient(String thePreviousClientId, String clientId); + void enqueueCheckChangeWatcher(ClassIDPair classIDPair, String[] fieldNames, String[] params); + void enqueueCheckChangeWatcher(ClassIDPair classIDPair, String[] fieldNames, String[] params, + IPerceroObject oldValue); } \ No newline at end of file diff --git a/src/main/java/com/percero/agents/sync/services/ISyncAgentService.java b/src/main/java/com/percero/agents/sync/services/ISyncAgentService.java index 019477c..b33f29b 100644 --- a/src/main/java/com/percero/agents/sync/services/ISyncAgentService.java +++ b/src/main/java/com/percero/agents/sync/services/ISyncAgentService.java @@ -29,8 +29,8 @@ public interface ISyncAgentService { public ServerResponse deleteObject(ClassIDPair theClassIdPair, String clientId, Boolean pushToClient) throws Exception; public ServerResponse deleteObjectById(String theClassName, String theId, String clientId) throws Exception; public ServerResponse deleteObjectById(String theClassName, String theId, String clientId, Boolean pushToClient) throws Exception; - public boolean systemDeleteObject(IPerceroObject perceroObject, String clientId, boolean pushToUser) throws Exception; - public boolean systemDeleteObject(IPerceroObject perceroObject, String clientId, boolean pushToUser, Collection deletedObjects) throws Exception; + public boolean systemDeleteObject(ClassIDPair classIdPair, String clientId, boolean pushToUser) throws Exception; + public boolean systemDeleteObject(ClassIDPair classIdPair, String clientId, boolean pushToUser, Collection deletedObjects) throws Exception; public ServerResponse putObject(IPerceroObject perceroObject, String transactionId, Date updateDate, String clientId) throws Exception; public ServerResponse putObject(IPerceroObject perceroObject, String transactionId, Date updateDate, String clientId, Boolean pushToClient) throws Exception; diff --git a/src/main/java/com/percero/agents/sync/services/RedisDataProvider.java b/src/main/java/com/percero/agents/sync/services/RedisDataProvider.java index a5634c3..b592fa5 100644 --- a/src/main/java/com/percero/agents/sync/services/RedisDataProvider.java +++ b/src/main/java/com/percero/agents/sync/services/RedisDataProvider.java @@ -447,4 +447,10 @@ public Map> getChangedMappedFields( // TODO Auto-generated method stub return null; } + + @Override + public IPerceroObject findById(ClassIDPair classIdPair, String userId, Boolean ignoreCache, Boolean shellOnly) { + // TODO Auto-generated method stub + return null; + } } diff --git a/src/main/java/com/percero/agents/sync/services/SyncAgentDataProvider.java b/src/main/java/com/percero/agents/sync/services/SyncAgentDataProvider.java index cee4f85..cfced4b 100644 --- a/src/main/java/com/percero/agents/sync/services/SyncAgentDataProvider.java +++ b/src/main/java/com/percero/agents/sync/services/SyncAgentDataProvider.java @@ -434,6 +434,10 @@ public IPerceroObject findById(ClassIDPair classIdPair, String userId) { } @Override public IPerceroObject findById(ClassIDPair classIdPair, String userId, Boolean ignoreCache) { + return findById(classIdPair, userId, false, false); + } + @Override + public IPerceroObject findById(ClassIDPair classIdPair, String userId, Boolean ignoreCache, Boolean shellOnly) { boolean hasReadQuery = false; IMappedClassManager mcm = MappedClassManagerFactory.getMappedClassManager(); MappedClass mappedClass = mcm.getMappedClassByClassName(classIdPair.getClassName()); @@ -463,7 +467,14 @@ public IPerceroObject findById(ClassIDPair classIdPair, String userId, Boolean i if (s == null) s = appSessionFactory.openSession(); // ((BaseDataObject)result).setIsClean(false); - result = (IPerceroObject) SyncHibernateUtils.cleanObject(result, s, userId); + if (shellOnly) { + IPerceroObject shellResult = result.getClass().newInstance(); + shellResult.setID(result.getID()); + result = shellResult; + } + else { + result = (IPerceroObject) SyncHibernateUtils.cleanObject(result, s, userId); + } return result; } else { diff --git a/src/main/java/com/percero/agents/sync/services/SyncAgentService.java b/src/main/java/com/percero/agents/sync/services/SyncAgentService.java index 72f5505..867ff35 100644 --- a/src/main/java/com/percero/agents/sync/services/SyncAgentService.java +++ b/src/main/java/com/percero/agents/sync/services/SyncAgentService.java @@ -1,5 +1,7 @@ package com.percero.agents.sync.services; +import java.io.IOException; + //import static org.springframework.data.mongodb.core.query.Criteria.where; import java.lang.reflect.InvocationTargetException; @@ -18,6 +20,8 @@ import javax.annotation.PostConstruct; import org.apache.log4j.Logger; +import org.codehaus.jackson.JsonGenerationException; +import org.codehaus.jackson.map.JsonMappingException; import org.codehaus.jackson.map.ObjectMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -29,7 +33,6 @@ import com.percero.agents.sync.access.IAccessManager; import com.percero.agents.sync.access.RedisKeyUtils; -import com.percero.agents.sync.connectors.HttpConnectorOperation; import com.percero.agents.sync.connectors.ILogicConnector; import com.percero.agents.sync.cw.IChangeWatcherHelperFactory; import com.percero.agents.sync.cw.IChangeWatcherValueHelper; @@ -50,6 +53,8 @@ import com.percero.agents.sync.metadata.MappedClass; import com.percero.agents.sync.metadata.MappedClassManagerFactory; import com.percero.agents.sync.metadata.MappedField; +import com.percero.agents.sync.metadata.MappedFieldList; +import com.percero.agents.sync.metadata.MappedFieldPerceroObject; import com.percero.agents.sync.rr.IRequestHandler; import com.percero.agents.sync.vo.BaseDataObject; import com.percero.agents.sync.vo.ClassIDPair; @@ -255,7 +260,7 @@ public void processManifest() { public String getDataId(String clientId) throws Exception { Boolean isValidClient = accessManager.validateClientByClientId(clientId); if (!isValidClient) - throw new ClientException(ClientException.INVALID_CLIENT, ClientException.INVALID_CLIENT_CODE); + throw new ClientException(ClientException.INVALID_CLIENT, ClientException.INVALID_CLIENT_CODE, "", clientId); String dataId = (String) cacheDataStore.listIndex(RedisKeyUtils.dataRecord()); @@ -272,14 +277,11 @@ public PerceroList getAllByName(String className, Boolean return return getAllByName(className, null, null, returnTotal, clientId); } - @SuppressWarnings("rawtypes") public PerceroList getAllByName(String className, Integer pageNumber, Integer pageSize, Boolean returnTotal, String clientId) throws Exception { Boolean isValidClient = accessManager.validateClientByClientId(clientId); if (!isValidClient) - throw new ClientException(ClientException.INVALID_CLIENT, ClientException.INVALID_CLIENT_CODE); + throw new ClientException(ClientException.INVALID_CLIENT, ClientException.INVALID_CLIENT_CODE, "", clientId); - Class theClass = MappedClass.forName(className); - // Get the MappedClass and determine which DataProvider provides data for this object. IMappedClassManager mcm = MappedClassManagerFactory.getMappedClassManager(); MappedClass mappedClass = mcm.getMappedClassByClassName(className); @@ -290,12 +292,9 @@ public PerceroList getAllByName(String className, Integer pageNu if (result != null) { // Register an Zero ID object to indicate that this user wants updates to ALL objects of this type. - Object newInstance = theClass.newInstance(); - if (newInstance instanceof IPerceroObject) { - ((IPerceroObject) newInstance).setID("0"); - - postGetHelper.postGetObject((IPerceroObject) newInstance, userId, clientId); - } + IPerceroObject newInstance = mappedClass.newPerceroObject(); + newInstance.setID("0"); + postGetHelper.postGetObject((IPerceroObject) newInstance, userId, clientId); } return result; @@ -307,7 +306,7 @@ public Map countAllByName(Collection classNames, String Boolean isValidClient = accessManager.validateClientByClientId(clientId); if (!isValidClient) - throw new ClientException(ClientException.INVALID_CLIENT, ClientException.INVALID_CLIENT_CODE); + throw new ClientException(ClientException.INVALID_CLIENT, ClientException.INVALID_CLIENT_CODE, "", clientId); Iterator itrClassNames = classNames.iterator(); while(itrClassNames.hasNext()) { @@ -332,7 +331,7 @@ public List runQuery(String className, String queryName, Object[] queryA Boolean isValidClient = accessManager.validateClientByClientId(clientId); if (!isValidClient) - throw new ClientException(ClientException.INVALID_CLIENT, ClientException.INVALID_CLIENT_CODE); + throw new ClientException(ClientException.INVALID_CLIENT, ClientException.INVALID_CLIENT_CODE, "", clientId); try { IMappedClassManager mcm = MappedClassManagerFactory.getMappedClassManager(); @@ -353,7 +352,7 @@ public Object runProcess(String processName, Object queryArguments, String clien Boolean isValidClient = accessManager.validateClientByClientId(clientId); if (!isValidClient) - throw new ClientException(ClientException.INVALID_CLIENT, ClientException.INVALID_CLIENT_CODE); + throw new ClientException(ClientException.INVALID_CLIENT, ClientException.INVALID_CLIENT_CODE, "", clientId); if (!StringUtils.hasText(processName)) throw new SyncException(SyncException.INVALID_PROCESS_ERROR, SyncException.INVALID_PROCESS_ERROR_CODE); @@ -384,26 +383,49 @@ public Object runProcess(String processName, Object queryArguments, String clien if (processHelper != null) { try { - Object args[] = new Object[2]; - args[0] = clientId; - args[1] = queryArguments; - Method method = processHelper.getClass().getDeclaredMethod(processName, new Class[]{String.class,Object[].class}); - result = method.invoke(processHelper, args); - } catch(Exception e) { - if (e instanceof InvocationTargetException) { - InvocationTargetException ite = (InvocationTargetException) e; - if (ite.getTargetException() instanceof SyncException) { - throw (SyncException) ite.getTargetException(); + Object[] args = null; + Method method = null; + try { + method = processHelper.getClass().getDeclaredMethod(processName, new Class[]{String.class,Object[].class}); + } catch(NoSuchMethodException nsme) {} + + if (method != null) { + // Attempt to see if a method exists with parameters as an array. + args = new Object[2]; + args[0] = clientId; + args[1] = (queryArguments instanceof List ? ((List)queryArguments).toArray() : queryArguments); + } + else { + try { + method = processHelper.getClass().getDeclaredMethod(processName, new Class[]{String.class,Object.class}); + } catch(NoSuchMethodException nsme) {} + + if (method != null) { + args = new Object[2]; + args[0] = clientId; + args[1] = queryArguments; } else { - log.error("Unable to run Process", ite.getTargetException()); - throw new SyncException("Error Running Process", -101, "Unable to run process " + processName + ":\n" + ite.getTargetException().getMessage()); + args = new Object[1]; + args[0] = clientId; + // Don't catch the "NoSuchMethodException" here since we + // have tried the last method signature, now we just + // fail + method = processHelper.getClass().getDeclaredMethod(processName, new Class[]{String.class}); } } + result = method.invoke(processHelper, args); + } catch(InvocationTargetException e) { + if (e.getTargetException() instanceof SyncException) { + throw (SyncException) e.getTargetException(); + } else { - log.error("Unable to run Process", e); - throw new SyncException("Error Running Process", -101, "Unable to run process " + processName + ":\n" + e.getMessage()); + throw new SyncException("Error Running Process", -101, "Unable to run process " + processName + ":\n" + e.getTargetException().getMessage(), e); } + } catch(NoSuchMethodException e) { + throw new SyncException("Error Running Process", -101, "Unable to run process " + processName + ":\n" + e.getMessage(), e); + } catch(Exception e) { + throw new SyncException("Error Running Process", -101, "Unable to run process " + processName + ":\n" + e.getMessage(), e); } } @@ -415,7 +437,7 @@ public Object getChangeWatcherValue(ClassIDPair classIdPair, String fieldName, S Boolean isValidClient = accessManager.validateClientByClientId(clientId); if (!isValidClient) - throw new ClientException(ClientException.INVALID_CLIENT, ClientException.INVALID_CLIENT_CODE); + throw new ClientException(ClientException.INVALID_CLIENT, ClientException.INVALID_CLIENT_CODE, "", clientId); // Only ChangeWatcherValueHelper's have the "get" function. IChangeWatcherValueHelper cwh = (IChangeWatcherValueHelper) changeWatcherHelperFactory.getHelper(classIdPair.getClassName()); @@ -434,7 +456,7 @@ public Object findByExample(Object theQueryObject, List excludeProperties, String clientId) throws Exception { Boolean isValidClient = accessManager.validateClientByClientId(clientId); if (!isValidClient) - throw new ClientException(ClientException.INVALID_CLIENT, ClientException.INVALID_CLIENT_CODE); + throw new ClientException(ClientException.INVALID_CLIENT, ClientException.INVALID_CLIENT_CODE, "", clientId); if (theQueryObject instanceof IPerceroObject) { IMappedClassManager mcm = MappedClassManagerFactory.getMappedClassManager(); @@ -442,7 +464,13 @@ public Object findByExample(Object theQueryObject, if (mappedClass != null) { String userId = accessManager.getClientUserId(clientId); IDataProvider dataProvider = dataProviderManager.getDataProviderByName(mappedClass.dataProviderName); - return dataProvider.findByExample((IPerceroObject) theQueryObject, excludeProperties, userId, false); + List result = dataProvider.findByExample((IPerceroObject) theQueryObject, excludeProperties, userId, false); + + if (result != null && !result.isEmpty()) { + postGetHelper.postGetObject(result, userId, clientId); + } + + return result; } } @@ -455,7 +483,7 @@ public List systemFindByExample(Object theQueryObject, List systemFindByExample(Object theQueryObject, List excludeProperties, String clientId) throws Exception { Boolean isValidClient = accessManager.validateClientByClientId(clientId); if (!isValidClient) - throw new ClientException(ClientException.INVALID_CLIENT, ClientException.INVALID_CLIENT_CODE); + throw new ClientException(ClientException.INVALID_CLIENT, ClientException.INVALID_CLIENT_CODE, "", clientId); IMappedClassManager mcm = MappedClassManagerFactory.getMappedClassManager(); MappedClass mappedClass = mcm.getMappedClassByClassName(theQueryObject.getClass().getCanonicalName()); @@ -513,7 +541,7 @@ public Object searchByExample(Object theQueryObject, IDataProvider dataProvider = dataProviderManager.getDataProviderByName(mappedClass.dataProviderName); List result = dataProvider.findByExample((IPerceroObject) theQueryObject, excludeProperties, userId, false); - if (result != null && result.size() > 0) { + if (result != null && !result.isEmpty()) { postGetHelper.postGetObject(result, userId, clientId); } @@ -531,7 +559,7 @@ public Object findById(ClassIDPair classIdPair, String clientId) throws Exceptio public Object findById(String aClassName, String anId, String clientId) throws Exception { Boolean isValidClient = accessManager.validateClientByClientId(clientId); if (!isValidClient) - throw new ClientException(ClientException.INVALID_CLIENT, ClientException.INVALID_CLIENT_CODE); + throw new ClientException(ClientException.INVALID_CLIENT, ClientException.INVALID_CLIENT_CODE, "", clientId); // Get the MappedClass and determine which DataProvider provides data for this object. IMappedClassManager mcm = MappedClassManagerFactory.getMappedClassManager(); @@ -643,7 +671,7 @@ public List getHistory(ClassIDPair classIdPair, String clientI public List getHistory(String aClassName, String anId, String clientId) throws Exception { Boolean isValidClient = accessManager.validateClientByClientId(clientId); if (!isValidClient) - throw new ClientException(ClientException.INVALID_CLIENT, ClientException.INVALID_CLIENT_CODE); + throw new ClientException(ClientException.INVALID_CLIENT, ClientException.INVALID_CLIENT_CODE, "", clientId); // Get the MappedClass and determine which DataProvider provides data for this object. IMappedClassManager mcm = MappedClassManagerFactory.getMappedClassManager(); @@ -666,7 +694,7 @@ public List findByIds(List classIdList, String cli Boolean isValidClient = accessManager.validateClientByClientId(clientId); if (!isValidClient) - throw new ClientException(ClientException.INVALID_CLIENT, ClientException.INVALID_CLIENT_CODE); + throw new ClientException(ClientException.INVALID_CLIENT, ClientException.INVALID_CLIENT_CODE, "", clientId); String userId = accessManager.getClientUserId(clientId); @@ -696,7 +724,7 @@ public ServerResponse putObject(IPerceroObject perceroObject, String transaction Boolean isValidClient = accessManager.validateClientByClientId(clientId); if (!isValidClient) - throw new ClientException(ClientException.INVALID_CLIENT, ClientException.INVALID_CLIENT_CODE); + throw new ClientException(ClientException.INVALID_CLIENT, ClientException.INVALID_CLIENT_CODE, "", clientId); // Get the MappedClass and determine which DataProvider provides data for this object. IMappedClassManager mcm = MappedClassManagerFactory.getMappedClassManager(); @@ -744,82 +772,8 @@ public ServerResponse putObject(IPerceroObject perceroObject, String transaction IDataProvider dataProvider = dataProviderManager.getDataProviderByName(mappedClass.dataProviderName); changedFields = dataProvider.getChangedMappedFields(perceroObject); if (changedFields == null || changedFields.size() > 0) { - // Something has changed. - result = dataProvider.putObject(perceroObject, changedFields, userId); - - if (result != null) { - // Now record the updated object. - ObjectModJournal newModJournal = new ObjectModJournal(); - newModJournal.setDateModified(updateDate); - newModJournal.setUserId(userId); - - if (transactionId == null || transactionId.length() == 0) - transactionId = UUID.randomUUID().toString(); - newModJournal.setTransactionId(transactionId); - newModJournal.setClassID(result.getID()); - newModJournal.setClassName(result.getClass().getName()); - - cacheDataStore.lpushListValue(RedisKeyUtils.objectModJournal(perceroObject.getClass().getCanonicalName(), perceroObject.getID()), newModJournal); - - // Also store historical record, if necessary. - // Get the Current object if this is a BaseHistoryObject. - if (storeHistory && (result instanceof IHistoryObject)) - { - HistoricalObject historyObject = new HistoricalObject(); - historyObject.setObjectVersion(result.classVersion()); - historyObject.setID(UUID.randomUUID().toString()); - historyObject.setObjectChangeDate(updateDate); - historyObject.setObjectClassName(result.getClass().getName()); - historyObject.setObjectId(result.getID()); - historyObject.setObjectChangerId(userId); - historyObject.setObjectData(safeObjectMapper.writeValueAsString(result)); - - cacheDataStore.lpushListValue(RedisKeyUtils.historicalObject(result.getClass().getCanonicalName(), result.getID()), historyObject); - } - -// if (taskExecutor != null && false) { -// taskExecutor.execute(new PostPutTask(postPutHelper, BaseDataObject.toClassIdPair((BaseDataObject) result), userId, clientId, pushToClient, changedFields)); -// } else { - postPutHelper.postPutObject(BaseDataObject.toClassIdPair((BaseDataObject) result), userId, clientId, pushToClient, changedFields); -// } - - // For each changed field, we look at the reverse mapped - // relationship (if one exists) and update that object - // (this was already done in the cache by the - // cacheManager). - if (changedFields != null && !changedFields.isEmpty()) { - Iterator>> itrChangedFieldEntryset = changedFields.entrySet().iterator(); - while (itrChangedFieldEntryset.hasNext()) { - Map.Entry> nextEntry = itrChangedFieldEntryset.next(); - ClassIDPair thePair = nextEntry.getKey(); - Collection changedMappedFields = nextEntry.getValue(); - - // If thePair is NOT the object being updated, then need to run the postPutHelper for the Pair object as well. - if (!thePair.equals(classIdPair)) { - Map> thePairChangedFields = new HashMap>(1); - thePairChangedFields.put(thePair, changedMappedFields); - - // This will also run thePair through the ChangeWatcher check below. -// if (taskExecutor != null && false) { -// taskExecutor.execute(new PostPutTask(postPutHelper, thePair, userId, clientId, pushToClient, thePairChangedFields)); -// } else { - postPutHelper.postPutObject(thePair, userId, clientId, pushToClient, thePairChangedFields); -// } - } - else { - Iterator itrChangedFields = changedMappedFields.iterator(); - String[] fieldNames = new String[changedMappedFields.size()]; - int i = 0; - while (itrChangedFields.hasNext()) { - MappedField nextChangedField = itrChangedFields.next(); - fieldNames[i] = nextChangedField.getField().getName(); - i++; - } - accessManager.checkChangeWatchers(thePair, fieldNames, null, null); - } - } - } - } + result = handleUpdateObject_ChangedFields(perceroObject, transactionId, updateDate, clientId, + pushToClient, changedFields, dataProvider); } else { log.info("Unnecessary PutObject: " + perceroObject.getClass().getCanonicalName() + " (" + perceroObject.getID() + ")"); @@ -886,45 +840,8 @@ public boolean systemPutObject(IPerceroObject perceroObject, String transactionI IDataProvider dataProvider = dataProviderManager.getDataProviderByName(mappedClass.dataProviderName); changedFields = dataProvider.getChangedMappedFields(perceroObject); if (changedFields == null || changedFields.size() > 0) { - result = dataProvider.putObject(perceroObject, changedFields, null); - - if (result != null) { - // Now record the updated object. - ObjectModJournal newModJournal = new ObjectModJournal(); - newModJournal.setID(UUID.randomUUID().toString()); - if (transactionId == null || transactionId.length() == 0) - transactionId = UUID.randomUUID().toString(); - newModJournal.setTransactionId(transactionId); - newModJournal.setClassID(result.getID()); - newModJournal.setClassName(result.getClass().getName()); - newModJournal.setDateModified(updateDate); - - cacheDataStore.lpushListValue(RedisKeyUtils.objectModJournal(result.getClass().getCanonicalName(), result.getID()), newModJournal); - - // Also store historical record, if necessary. - // Get the Current object if this is a BaseHistoryObject. - if (storeHistory && (result instanceof IHistoryObject)) - { - HistoricalObject historyObject = new HistoricalObject(); - historyObject.setObjectVersion(result.classVersion()); - historyObject.setID(UUID.randomUUID().toString()); - historyObject.setObjectChangeDate(updateDate); - historyObject.setObjectClassName(result.getClass().getName()); - historyObject.setObjectId(result.getID()); - historyObject.setObjectChangerId(userId); - historyObject.setObjectData(safeObjectMapper.writeValueAsString(result)); - - cacheDataStore.lpushListValue(RedisKeyUtils.historicalObject(result.getClass().getCanonicalName(), result.getID()), historyObject); - - //syncSession.save(historyObject); - } - - if (taskExecutor != null && false) { - taskExecutor.execute(new PostPutTask(postPutHelper, BaseDataObject.toClassIdPair((BaseDataObject) result), userId, null, pushToUser, changedFields)); - } else { - postPutHelper.postPutObject(BaseDataObject.toClassIdPair((BaseDataObject) result), userId, null, pushToUser, changedFields); - } - } + result = handleUpdateObject_ChangedFields(perceroObject, transactionId, updateDate, userId, + pushToUser, changedFields, dataProvider); } else { log.info("Unnecessary PutObject: " + perceroObject.getClass().getCanonicalName() + " (" + perceroObject.getID() + ")"); @@ -939,6 +856,67 @@ public boolean systemPutObject(IPerceroObject perceroObject, String transactionI return (result != null); } + /** + * @param perceroObject + * @param transactionId + * @param updateDate + * @param userId + * @param pushToUser + * @param changedFields + * @param dataProvider + * @return + * @throws SyncException + * @throws IOException + * @throws JsonGenerationException + * @throws JsonMappingException + * @throws Exception + */ + private IPerceroObject handleUpdateObject_ChangedFields(IPerceroObject perceroObject, String transactionId, + Date updateDate, String clientId, boolean pushToClient, Map> changedFields, + IDataProvider dataProvider) + throws SyncException, IOException, JsonGenerationException, JsonMappingException, Exception { + IPerceroObject result; + + String userId = accessManager.getClientUserId(clientId); + result = dataProvider.putObject(perceroObject, changedFields, userId); + + if (result != null) { + // Now record the updated object. + ObjectModJournal newModJournal = new ObjectModJournal(); + newModJournal.setID(UUID.randomUUID().toString()); + if (transactionId == null || transactionId.length() == 0) + transactionId = UUID.randomUUID().toString(); + newModJournal.setTransactionId(transactionId); + newModJournal.setClassID(result.getID()); + newModJournal.setClassName(result.getClass().getName()); + newModJournal.setDateModified(updateDate); + + cacheDataStore.lpushListValue(RedisKeyUtils.objectModJournal(result.getClass().getCanonicalName(), result.getID()), newModJournal); + + // Also store historical record, if necessary. + // Get the Current object if this is a BaseHistoryObject. + if (storeHistory && (result instanceof IHistoryObject)) { + HistoricalObject historyObject = new HistoricalObject(); + historyObject.setObjectVersion(result.classVersion()); + historyObject.setID(UUID.randomUUID().toString()); + historyObject.setObjectChangeDate(updateDate); + historyObject.setObjectClassName(result.getClass().getName()); + historyObject.setObjectId(result.getID()); + historyObject.setObjectChangerId(userId); + historyObject.setObjectData(safeObjectMapper.writeValueAsString(result)); + + cacheDataStore.lpushListValue(RedisKeyUtils.historicalObject(result.getClass().getCanonicalName(), result.getID()), historyObject); + } + + if (taskExecutor != null && false) { + taskExecutor.execute(new PostPutTask(postPutHelper, BaseDataObject.toClassIdPair((BaseDataObject) result), userId, null, pushToClient, changedFields)); + } else { + postPutHelper.postPutObject(BaseDataObject.toClassIdPair((BaseDataObject) result), userId, null, pushToClient, changedFields); + } + } + return result; + } + public ServerResponse createObject(IPerceroObject perceroObject, String clientId) throws Exception { return createObject(perceroObject, clientId, false); } @@ -950,7 +928,7 @@ public ServerResponse createObject(IPerceroObject perceroObject, String clientId Boolean isValidClient = accessManager.validateClientByClientId(clientId); if (!isValidClient) - throw new ClientException(ClientException.INVALID_CLIENT, ClientException.INVALID_CLIENT_CODE); + throw new ClientException(ClientException.INVALID_CLIENT, ClientException.INVALID_CLIENT_CODE, "", clientId); if (perceroObject != null) { String userId = accessManager.getClientUserId(clientId); @@ -1125,7 +1103,7 @@ public ServerResponse deleteObject(ClassIDPair theClassIdPair, String clientId, Boolean isValidClient = accessManager.validateClientByClientId(clientId); if (!isValidClient) - throw new ClientException(ClientException.INVALID_CLIENT, ClientException.INVALID_CLIENT_CODE); + throw new ClientException(ClientException.INVALID_CLIENT, ClientException.INVALID_CLIENT_CODE, "", clientId); try { @@ -1139,13 +1117,7 @@ public ServerResponse deleteObject(ClassIDPair theClassIdPair, String clientId, hasAccess = dataProvider.getDeleteAccess(theClassIdPair, userId); if (hasAccess) { - IPerceroObject perceroObject = dataProvider.findById(theClassIdPair, null); - if (perceroObject == null) { - response.setIsSuccessful(true); - return response; - } - - if (systemDeleteObject(perceroObject, clientId, pushToClient, new HashSet())) { + if (systemDeleteObject(theClassIdPair, clientId, pushToClient)) { response.setIsSuccessful(true); } else { @@ -1164,164 +1136,266 @@ public ServerResponse deleteObject(ClassIDPair theClassIdPair, String clientId, return response; } - public boolean systemDeleteObject(IPerceroObject perceroObject, String clientId, boolean pushToUser) throws Exception { - return systemDeleteObject(perceroObject, clientId, pushToUser, new HashSet()); + public boolean systemDeleteObject(ClassIDPair classIdPair, String clientId, boolean pushToUser) throws Exception { + return systemDeleteObject(classIdPair, clientId, pushToUser, new HashSet()); } - public boolean systemDeleteObject(IPerceroObject perceroObject, String clientId, boolean pushToUser, Collection deletedObjects) throws Exception { - boolean result = true; - if(perceroObject == null) + + /** + * - The pushes out notifications to ALL related objects. + * - The DataProvider should handle the updated target objects by updating the cache (IFF they are in the cache) + * - The updated objects source objects are handled by the cascadeRemoveFieldReferences and nulledOnRemoveFieldReferences + * since these objects need to be deleted/updated in the data store as well. + * @param classIdPair + * @param clientId + * @param pushToUser + * @param deletedObjects + * @return + * @throws Exception + */ + public boolean systemDeleteObject(ClassIDPair classIdPair, String clientId, boolean pushToUser, + Collection deletedObjects) throws Exception { + + // Validate our input + if(classIdPair == null) return true; - if (deletedObjects.contains(perceroObject)) + if (deletedObjects.contains(classIdPair)) return true; else - deletedObjects.add(perceroObject); + deletedObjects.add(classIdPair); String userId = accessManager.getClientUserId(clientId); IMappedClassManager mcm = MappedClassManagerFactory.getMappedClassManager(); - MappedClass mappedClass = mcm.getMappedClassByClassName(perceroObject.getClass().getName()); - if (mappedClass != null) { - IDataProvider dataProvider = dataProviderManager.getDataProviderByName(mappedClass.dataProviderName); - - perceroObject = (IPerceroObject) dataProvider.findById(new ClassIDPair(perceroObject.getID(), perceroObject.getClass().getCanonicalName()), null); + MappedClass mappedClass = mcm.getMappedClassByClassName(classIdPair.getClassName()); + if (mappedClass == null) { + return false; + } + + IDataProvider dataProvider = dataProviderManager.getDataProviderByName(mappedClass.dataProviderName); + + IPerceroObject perceroObject = (IPerceroObject) dataProvider.findById(classIdPair, null); + + if (perceroObject == null) { + return true; + } + + handleDeleteObject_CascadeRemove(perceroObject, mappedClass, deletedObjects, clientId); + + handleDeleteObject_CascadeNull(perceroObject, mappedClass, clientId); + + // This is a list of objects that need to be notified that they have + // somehow changed. That somehow is directly linked to these objects + // relationship with the deleted object. + + if (dataProvider.deleteObject(BaseDataObject.toClassIdPair(perceroObject), userId)) { + // Also store historical record, if necessary. + if ((storeHistory != null && storeHistory.booleanValue()) && (perceroObject instanceof IHistoryObject)) + { + try { + HistoricalObject historyObject = new HistoricalObject(); + historyObject.setObjectVersion(perceroObject.classVersion()); + historyObject.setID(UUID.randomUUID().toString()); + historyObject.setObjectChangeDate(new Date()); + historyObject.setObjectClassName(perceroObject.getClass().getName()); + historyObject.setObjectId(perceroObject.getID()); + historyObject.setObjectChangerId(userId); + historyObject.setObjectData(safeObjectMapper.writeValueAsString(perceroObject)); + + cacheDataStore.lpushListValue(RedisKeyUtils.historicalObject(perceroObject.getClass().getCanonicalName(), perceroObject.getID()), historyObject); + } catch(Exception e) { + log.warn("Unable to save HistoricalObject in deleteObject", e); + } + } - if (perceroObject == null) { - return true; + if (taskExecutor != null && false) { + taskExecutor.execute(new PostDeleteTask(postDeleteHelper, perceroObject, userId, clientId, pushToUser)); + } else { + postDeleteHelper.postDeleteObject(perceroObject, userId, clientId, pushToUser); } + + return true; + } - Iterator> itrCascadeRemoveFieldReferencesEntrySet = mappedClass.cascadeRemoveFieldReferences.entrySet().iterator(); - while (itrCascadeRemoveFieldReferencesEntrySet.hasNext()) { - Map.Entry nextEntry = itrCascadeRemoveFieldReferencesEntrySet.next(); - MappedField nextRemoveMappedFieldRef = nextEntry.getKey(); - try { - MappedField nextMappedField = nextEntry.getValue(); - if (nextMappedField == null) { - // There is no direct link from mappedClass, so need to get all by example. - IPerceroObject tempObject = (IPerceroObject) nextRemoveMappedFieldRef.getMappedClass().clazz.newInstance(); - nextRemoveMappedFieldRef.getSetter().invoke(tempObject, perceroObject); - IDataProvider dataProviderRef = dataProviderManager.getDataProviderByName(nextRemoveMappedFieldRef.getMappedClass().dataProviderName); - List referencingObjectsNew = dataProviderRef.findAllRelatedObjects(perceroObject, nextMappedField, false, null); - List referencingObjects = dataProviderRef.findByExample(tempObject, null, null, false); - Iterator itrReferencingObjects = referencingObjects.iterator(); - while (itrReferencingObjects.hasNext()) { - IPerceroObject nextReferencingObject = itrReferencingObjects.next(); - systemDeleteObject(nextReferencingObject, clientId, true, deletedObjects); + return false; + } + + /** + * Given the perceroObject, goes through each relationship where the related + * object relationship must be set to NULL before perceroObject can be + * removed -> cascade update. + * + * @param perceroObject + * @param mappedClass + * @param userId + * @throws SyncDataException + */ + private void handleDeleteObject_CascadeNull(IPerceroObject perceroObject, MappedClass mappedClass, String clientId) + throws SyncDataException { + Iterator> itrNulledOnRemoveFieldReferencesEntrySet = mappedClass.getNulledOnRemoveFieldReferences().entrySet().iterator(); + while (itrNulledOnRemoveFieldReferencesEntrySet.hasNext()) { + Map.Entry nextEntry = itrNulledOnRemoveFieldReferencesEntrySet.next(); + MappedField nextToNullMappedFieldRef = nextEntry.getKey(); + try { + MappedField nextMappedField = nextEntry.getValue(); + if (nextMappedField == null) { + // There is no direct link from mappedClass, so need to get all by example. + IPerceroObject tempObject = (IPerceroObject) nextToNullMappedFieldRef.getMappedClass().newPerceroObject(); + nextToNullMappedFieldRef.getSetter().invoke(tempObject, perceroObject); + IDataProvider dataProviderRef = dataProviderManager.getDataProviderByName(nextToNullMappedFieldRef.getMappedClass().dataProviderName); + List referencingObjects = dataProviderRef.findByExample(tempObject, null, null, false); + Iterator itrReferencingObjects = referencingObjects.iterator(); + while (itrReferencingObjects.hasNext()) { + IPerceroObject nextReferencingObject = itrReferencingObjects.next(); + nextToNullMappedFieldRef.setToNull(nextReferencingObject); +// systemPutObject((IPerceroObject) nextReferencingObject, null, new Date(), userId, true); +// +// TODO: Is this better? + Map> changedFields = new HashMap>(1); + Collection mappedFields = new ArrayList(1); + mappedFields.add(nextToNullMappedFieldRef); + changedFields.put(BaseDataObject.toClassIdPair(nextReferencingObject), mappedFields); + handleUpdateObject_ChangedFields((IPerceroObject) nextReferencingObject, null, new Date(), clientId, true, changedFields, dataProviderManager.getDataProviderByName(nextToNullMappedFieldRef.getMappedClass().dataProviderName)); + } + } + else { + // Since perceroObject is fully loaded, we have the list hanging off of perceroObject + if (nextMappedField instanceof MappedFieldList) { + List referencingObjects = (List) nextMappedField.getGetter().invoke(perceroObject); + if (referencingObjects != null && !referencingObjects.isEmpty()) { + for(IPerceroObject nextReferencingObject : referencingObjects) { + nextReferencingObject = systemGetByObject(nextReferencingObject); + nextToNullMappedFieldRef.setToNull(nextReferencingObject); +// systemPutObject((IPerceroObject) nextReferencingObject, null, new Date(), userId, true); + Map> changedFields = new HashMap>(1); + Collection mappedFields = new ArrayList(1); + mappedFields.add(nextToNullMappedFieldRef); + changedFields.put(BaseDataObject.toClassIdPair(nextReferencingObject), mappedFields); + handleUpdateObject_ChangedFields((IPerceroObject) nextReferencingObject, null, new Date(), clientId, true, changedFields, dataProviderManager.getDataProviderByName(nextToNullMappedFieldRef.getMappedClass().dataProviderName)); + } + + // Now remove these objects from teh list. + referencingObjects.clear(); + } + } + else if (nextMappedField instanceof MappedFieldPerceroObject) { + IPerceroObject referencingObject = (IPerceroObject) nextMappedField.getGetter().invoke(perceroObject); + if (referencingObject != null) { + referencingObject = systemGetByObject(referencingObject); + nextToNullMappedFieldRef.setToNull(referencingObject); +// systemPutObject((IPerceroObject) referencingObject, null, new Date(), userId, true); + Map> changedFields = new HashMap>(1); + Collection mappedFields = new ArrayList(1); + mappedFields.add(nextToNullMappedFieldRef); + changedFields.put(BaseDataObject.toClassIdPair(referencingObject), mappedFields); + handleUpdateObject_ChangedFields((IPerceroObject) referencingObject, null, new Date(), clientId, true, changedFields, dataProviderManager.getDataProviderByName(nextToNullMappedFieldRef.getMappedClass().dataProviderName)); + + // Now remove the reference to this object. + nextMappedField.setToNull(perceroObject); } } else { - // We have the reverse lookup right here. - IPerceroObject tempObject = (IPerceroObject) nextRemoveMappedFieldRef.getMappedClass().clazz.newInstance(); - nextRemoveMappedFieldRef.getSetter().invoke(tempObject, perceroObject); - IDataProvider dataProviderRef = dataProviderManager.getDataProviderByName(nextRemoveMappedFieldRef.getMappedClass().dataProviderName); - List referencingObjectsNew = dataProviderRef.findAllRelatedObjects(perceroObject, nextMappedField, false, null); + // Fall back case if we don't have a special way of handling this type of MappedField + IPerceroObject tempObject = (IPerceroObject) nextToNullMappedFieldRef.getMappedClass().clazz.newInstance(); + nextToNullMappedFieldRef.getSetter().invoke(tempObject, perceroObject); + IDataProvider dataProviderRef = dataProviderManager.getDataProviderByName(nextToNullMappedFieldRef.getMappedClass().dataProviderName); List referencingObjects = dataProviderRef.findByExample(tempObject, null, null, false); Iterator itrReferencingObjects = referencingObjects.iterator(); while (itrReferencingObjects.hasNext()) { IPerceroObject nextReferencingObject = itrReferencingObjects.next(); - systemDeleteObject(nextReferencingObject, clientId, true, deletedObjects); + nextToNullMappedFieldRef.setToNull(nextReferencingObject); +// systemPutObject((IPerceroObject) nextReferencingObject, null, new Date(), userId, true); + Map> changedFields = new HashMap>(1); + Collection mappedFields = new ArrayList(1); + mappedFields.add(nextToNullMappedFieldRef); + changedFields.put(BaseDataObject.toClassIdPair(nextReferencingObject), mappedFields); + handleUpdateObject_ChangedFields((IPerceroObject) nextReferencingObject, null, new Date(), clientId, true, changedFields, dataProviderManager.getDataProviderByName(nextToNullMappedFieldRef.getMappedClass().dataProviderName)); } } - } catch(Exception e) { - log.error("Unable to remove referenced object", e); - result = false; } + } catch(Exception e) { + throw new SyncDataException(SyncDataException.DELETE_OBJECT_ERROR, SyncDataException.DELETE_OBJECT_ERROR_CODE, e); } + } + } - Object[] nullObject = new Object[1]; - nullObject[0] = null; - Iterator> itrNulledOnRemoveFieldReferencesEntrySet = mappedClass.nulledOnRemoveFieldReferences.entrySet().iterator(); - while (itrNulledOnRemoveFieldReferencesEntrySet.hasNext()) { - Map.Entry nextEntry = itrNulledOnRemoveFieldReferencesEntrySet.next(); - MappedField nextToNullMappedFieldRef = nextEntry.getKey(); - try { - MappedField nextMappedField = nextEntry.getValue(); - if (nextMappedField == null) { - // There is no direct link from mappedClass, so need to get all by example. - IPerceroObject tempObject = (IPerceroObject) nextToNullMappedFieldRef.getMappedClass().clazz.newInstance(); - nextToNullMappedFieldRef.getSetter().invoke(tempObject, perceroObject); - IDataProvider dataProviderRef = dataProviderManager.getDataProviderByName(nextToNullMappedFieldRef.getMappedClass().dataProviderName); - List referencingObjectsNew = dataProviderRef.findAllRelatedObjects(perceroObject, nextMappedField, false, null); - List referencingObjects = dataProviderRef.findByExample(tempObject, null, null, false); - Iterator itrReferencingObjects = referencingObjects.iterator(); - while (itrReferencingObjects.hasNext()) { - IPerceroObject nextReferencingObject = itrReferencingObjects.next(); - nextToNullMappedFieldRef.getSetter().invoke(nextReferencingObject, nullObject); - systemPutObject((IPerceroObject) nextReferencingObject, null, new Date(), userId, true); + /** + * Given the perceroObject, goes through each relationship where the related + * object must be removed before perceroObject can be removed -> cascade + * remove. + * + * @param perceroObject + * @param mappedClass + * @param deletedObjects + * @param clientId + */ + private void handleDeleteObject_CascadeRemove(IPerceroObject perceroObject, + MappedClass mappedClass, + Collection deletedObjects, + String clientId) throws SyncDataException { + + Iterator> itrCascadeRemoveFieldReferencesEntrySet = mappedClass.getCascadeRemoveFieldReferences().entrySet().iterator(); + while (itrCascadeRemoveFieldReferencesEntrySet.hasNext()) { + Map.Entry nextEntry = itrCascadeRemoveFieldReferencesEntrySet.next(); + MappedField nextRemoveMappedFieldRef = nextEntry.getKey(); + try { + MappedField nextMappedField = nextEntry.getValue(); + if (nextMappedField == null) { + // There is no direct link from mappedClass, so need to get all by example. + IPerceroObject tempObject = (IPerceroObject) nextRemoveMappedFieldRef.getMappedClass().newPerceroObject(); + nextRemoveMappedFieldRef.getSetter().invoke(tempObject, perceroObject); + IDataProvider dataProviderRef = dataProviderManager.getDataProviderByName(nextRemoveMappedFieldRef.getMappedClass().dataProviderName); + List referencingObjects = dataProviderRef.findByExample(tempObject, null, null, false); + Iterator itrReferencingObjects = referencingObjects.iterator(); + while (itrReferencingObjects.hasNext()) { + IPerceroObject nextReferencingObject = itrReferencingObjects.next(); + systemDeleteObject(BaseDataObject.toClassIdPair(nextReferencingObject), clientId, true, deletedObjects); + } + } + else { + // Since perceroObject is fully loaded, we have the list hanging off of perceroObject + if (nextMappedField instanceof MappedFieldList) { + List referencingObjects = (List) nextMappedField.getGetter().invoke(perceroObject); + if (referencingObjects != null && !referencingObjects.isEmpty()) { + for(IPerceroObject nextReferencingObject : referencingObjects) { + systemDeleteObject(BaseDataObject.toClassIdPair(nextReferencingObject), clientId, true, deletedObjects); + } + + // Now remove these objects from the list. + referencingObjects.clear(); + } + } + else if (nextMappedField instanceof MappedFieldPerceroObject) { + IPerceroObject referencingObject = (IPerceroObject) nextMappedField.getGetter().invoke(perceroObject); + if (referencingObject != null) { + systemDeleteObject(BaseDataObject.toClassIdPair(referencingObject), clientId, true, deletedObjects); + + // Now remove the refernce to this object. + nextMappedField.setToNull(perceroObject); } } else { - // We have the reverse lookup right here. - IPerceroObject tempObject = (IPerceroObject) nextToNullMappedFieldRef.getMappedClass().clazz.newInstance(); - nextToNullMappedFieldRef.getSetter().invoke(tempObject, perceroObject); - IDataProvider dataProviderRef = dataProviderManager.getDataProviderByName(nextToNullMappedFieldRef.getMappedClass().dataProviderName); - List referencingObjectsNew = dataProviderRef.findAllRelatedObjects(perceroObject, nextMappedField, false, null); + // Fall back case if we don't have a special way of handling this type of MappedField + IPerceroObject tempObject = (IPerceroObject) nextRemoveMappedFieldRef.getMappedClass().clazz.newInstance(); + nextRemoveMappedFieldRef.getSetter().invoke(tempObject, perceroObject); + IDataProvider dataProviderRef = dataProviderManager.getDataProviderByName(nextRemoveMappedFieldRef.getMappedClass().dataProviderName); List referencingObjects = dataProviderRef.findByExample(tempObject, null, null, false); Iterator itrReferencingObjects = referencingObjects.iterator(); while (itrReferencingObjects.hasNext()) { IPerceroObject nextReferencingObject = itrReferencingObjects.next(); - nextToNullMappedFieldRef.getSetter().invoke(nextReferencingObject, nullObject); - systemPutObject((IPerceroObject) nextReferencingObject, null, new Date(), userId, true); + systemDeleteObject(BaseDataObject.toClassIdPair(nextReferencingObject), clientId, true, deletedObjects); } } - } catch(Exception e) { - log.error("Unable to remove referenced object", e); - result = false; - } - } - - Map objectsToUpdate = mappedClass.getRelatedClassIdPairMappedFieldMap(perceroObject, false); - - // If the result has been set to false, it means that deletion/update of one of the related objects failed. - if (result && dataProvider.deleteObject(BaseDataObject.toClassIdPair(perceroObject), null)) { - // Also store historical record, if necessary. - if (storeHistory && (perceroObject instanceof IHistoryObject)) - { - try { - HistoricalObject historyObject = new HistoricalObject(); - historyObject.setObjectVersion(perceroObject.classVersion()); - historyObject.setID(UUID.randomUUID().toString()); - historyObject.setObjectChangeDate(new Date()); - historyObject.setObjectClassName(perceroObject.getClass().getName()); - historyObject.setObjectId(perceroObject.getID()); - historyObject.setObjectChangerId(userId); - historyObject.setObjectData(safeObjectMapper.writeValueAsString(perceroObject)); - - cacheDataStore.lpushListValue(RedisKeyUtils.historicalObject(perceroObject.getClass().getCanonicalName(), perceroObject.getID()), historyObject); - } catch(Exception e) { - log.warn("Unable to save HistoricalObject in deleteObject", e); - } - } - - if (taskExecutor != null && false) { - taskExecutor.execute(new PostDeleteTask(postDeleteHelper, perceroObject, userId, clientId, pushToUser)); - } else { - postDeleteHelper.postDeleteObject(perceroObject, userId, clientId, pushToUser); - } - - Iterator> itrObjectsToUpdate = objectsToUpdate.entrySet().iterator(); - while (itrObjectsToUpdate.hasNext()) { - Entry nextObjectToUpdate = itrObjectsToUpdate.next(); - Map> changedFields = new HashMap>(); - Collection changedMappedFields = new ArrayList(1); - changedMappedFields.add(nextObjectToUpdate.getValue()); - changedFields.put(nextObjectToUpdate.getKey(), changedMappedFields); - postPutHelper.postPutObject(nextObjectToUpdate.getKey(), userId, clientId, true, changedFields); } - - result = true; - } - else { - result = false; + } catch(Exception e) { + throw new SyncDataException(SyncDataException.DELETE_OBJECT_ERROR, SyncDataException.DELETE_OBJECT_ERROR_CODE, e); } } - - return result; } public void updatesReceived(ClassIDPair[] theObjects, String clientId) throws Exception { Boolean isValidClient = accessManager.validateClientByClientId(clientId); if (!isValidClient) - throw new ClientException(ClientException.INVALID_CLIENT, ClientException.INVALID_CLIENT_CODE); + throw new ClientException(ClientException.INVALID_CLIENT, ClientException.INVALID_CLIENT_CODE, "", clientId); // Remove UpdateJournals for this client. accessManager.deleteUpdateJournals(clientId, theObjects); @@ -1330,7 +1404,7 @@ public void updatesReceived(ClassIDPair[] theObjects, String clientId) throws Ex public void deletesReceived(ClassIDPair[] theObjects, String clientId) throws Exception { Boolean isValidClient = accessManager.validateClientByClientId(clientId); if (!isValidClient) - throw new ClientException(ClientException.INVALID_CLIENT, ClientException.INVALID_CLIENT_CODE); + throw new ClientException(ClientException.INVALID_CLIENT, ClientException.INVALID_CLIENT_CODE, "", clientId); // Remove DeleteJournals for this client. accessManager.deleteDeleteJournals(clientId, theObjects); @@ -1361,7 +1435,7 @@ public Boolean pushClientUpdateJournals(String clientId, Collection list try { // Optimization: create the JSON string of the object. String userId = accessManager.getClientUserId(clientId); - String objectJson = ""; + StringBuilder objectJson = new StringBuilder(); int counter = 0; PushUpdateResponse pushUpdateResponse = null; @@ -1392,8 +1466,8 @@ public Boolean pushClientUpdateJournals(String clientId, Collection list pushUpdateResponse.getObjectList().add((BaseDataObject) anObject); if (anObject instanceof IPerceroObject) { if (counter> 0) - objectJson += ","; - objectJson += ((BaseDataObject)anObject).toJson(); + objectJson.append(','); + objectJson.append(((BaseDataObject)anObject).toJson()); counter++; } } @@ -1401,7 +1475,7 @@ public Boolean pushClientUpdateJournals(String clientId, Collection list if (pushUpdateResponse != null && counter > 0) { //pushObjectToRabbit(pushUpdateResponse, clientId); - pushUpdateResponse.setObjectJson(objectJson); + pushUpdateResponse.setObjectJson(objectJson.toString()); pushSyncHelper.pushSyncResponseToClient(pushUpdateResponse, clientId); //pushSyncHelper.pushJsonToRouting(pushUpdateResponse.toJson(objectJson, safeObjectMapper), PushUpdateResponse.class, clientId); } @@ -1426,7 +1500,7 @@ public Boolean pushClientDeleteJournals(String clientId, Collection list if (listDeleteJournals.size() > 0) { try { // Optimization: create the JSON string of the object. - String objectJson = ""; + StringBuilder objectJson = new StringBuilder(); int counter = 0; PushDeleteResponse pushDeleteResponse = null; @@ -1455,15 +1529,15 @@ public Boolean pushClientDeleteJournals(String clientId, Collection list if (classIdPair != null) { pushDeleteResponse.getObjectList().add(classIdPair); if (counter> 0) - objectJson += ","; - objectJson += classIdPair.toJson(); + objectJson.append(','); + objectJson.append(classIdPair.toJson()); counter++; } } if (pushDeleteResponse != null) { //pushObjectToRabbit(pushDeleteResponse, clientId); - pushDeleteResponse.setObjectJson(objectJson); + pushDeleteResponse.setObjectJson(objectJson.toString()); pushSyncHelper.pushSyncResponseToClient(pushDeleteResponse, clientId); //pushSyncHelper.pushJsonToRouting(pushDeleteResponse.toJson(objectJson, safeObjectMapper), PushDeleteResponse.class, clientId); } diff --git a/src/main/java/com/percero/agents/sync/vo/BaseDataObject.java b/src/main/java/com/percero/agents/sync/vo/BaseDataObject.java index a641f1f..2825e85 100644 --- a/src/main/java/com/percero/agents/sync/vo/BaseDataObject.java +++ b/src/main/java/com/percero/agents/sync/vo/BaseDataObject.java @@ -12,10 +12,8 @@ import javax.persistence.Transient; import org.apache.log4j.Logger; -import org.codehaus.jackson.annotate.JsonIgnoreProperties; import org.codehaus.jackson.annotate.JsonTypeInfo; import org.codehaus.jackson.map.ObjectMapper; -import org.mortbay.log.Log; import com.google.gson.JsonElement; import com.google.gson.JsonObject; @@ -25,7 +23,6 @@ import com.percero.agents.sync.manager.DataExternalizer; import com.percero.agents.sync.metadata.MappedClass; import com.percero.agents.sync.metadata.MappedClass.MappedClassMethodPair; -import com.percero.agents.sync.services.SyncAgentService; import com.percero.framework.vo.IPerceroObject; import com.percero.serial.JsonUtils; @@ -92,10 +89,14 @@ public String toJson(ObjectMapper objectMapper) { } public String toJson(Boolean encloseString, ObjectMapper objectMapper) { - String objectJson = retrieveJson(objectMapper); - if (encloseString) - objectJson = "{" + objectJson + "}"; - return objectJson; + StringBuilder objectJson; + if (encloseString) { + objectJson = new StringBuilder("{").append(retrieveJson(objectMapper)).append('}'); + } + else { + objectJson = new StringBuilder(retrieveJson(objectMapper)); + } + return objectJson.toString(); } public String retrieveJson(ObjectMapper objectMapper) { @@ -107,25 +108,27 @@ public String toEmbeddedJson() { } public String toEmbeddedJson(Boolean encloseString) { - String objectJson = retrieveEmbeddedJson(); - if (encloseString) - objectJson = "{" + objectJson + "}"; - return objectJson; + StringBuilder objectJson; + if (encloseString) { + objectJson = new StringBuilder("{").append("\"className\":\"").append(getClass().getCanonicalName()).append("\",").append("\"ID\":\"").append(getID()).append("\"").append('}'); + } + else { + objectJson = new StringBuilder("\"className\":\"").append(getClass().getCanonicalName()).append("\",").append("\"ID\":\"").append(getID()).append("\""); + } + return objectJson.toString(); } public String retrieveBaseJson() { - String objectJson = "\"cn\":\"" + getClass().getCanonicalName() + "\"," + - "\"ID\":\"" + getID() + "\""; + StringBuilder objectJson = new StringBuilder("\"cn\":\"").append(getClass().getCanonicalName()).append("\",").append("\"ID\":\"").append(getID()).append("\""); //+ "\"dataSource\":\"" + getDataSource() + "\"" ; - return objectJson; + return objectJson.toString(); } public String retrieveEmbeddedJson() { - String objectJson = "\"className\":\"" + getClass().getCanonicalName() + "\"," + - "\"ID\":\"" + getID() + "\""; + StringBuilder objectJson = new StringBuilder("\"className\":\"").append(getClass().getCanonicalName()).append("\",").append("\"ID\":\"").append(getID()).append("\""); - return objectJson; + return objectJson.toString(); } public void fromJson(String jsonString) { diff --git a/src/main/java/com/percero/agents/sync/vo/FindByIdResponse.java b/src/main/java/com/percero/agents/sync/vo/FindByIdResponse.java index f9ffeff..546204a 100644 --- a/src/main/java/com/percero/agents/sync/vo/FindByIdResponse.java +++ b/src/main/java/com/percero/agents/sync/vo/FindByIdResponse.java @@ -16,8 +16,7 @@ public void setResult(BaseDataObject result) { @Override public String retrieveBaseJson(ObjectMapper objectMapper) { - String objectJson = super.retrieveBaseJson(objectMapper) + ","; - objectJson += "\"result\":" + (getResult() == null ? "null" : getResult().toJson(objectMapper)); + String objectJson = super.retrieveBaseJson(objectMapper) + ",\"result\":" + (getResult() == null ? "null" : getResult().toJson(objectMapper)); return objectJson; } } diff --git a/src/main/java/com/percero/agents/sync/vo/FindByIdsResponse.java b/src/main/java/com/percero/agents/sync/vo/FindByIdsResponse.java index 91c11a1..9b31c70 100644 --- a/src/main/java/com/percero/agents/sync/vo/FindByIdsResponse.java +++ b/src/main/java/com/percero/agents/sync/vo/FindByIdsResponse.java @@ -17,15 +17,15 @@ public void setResult(List result) { @Override public String retrieveBaseJson(ObjectMapper objectMapper) { - String objectJson = super.retrieveBaseJson(objectMapper) + ",\"result\":["; + StringBuilder objectJson = new StringBuilder(super.retrieveBaseJson(objectMapper) + ",\"result\":["); int counter = 0; for(BaseDataObject nextBDO : getResult()) { if (counter > 0) - objectJson += ","; - objectJson += nextBDO.toJson(objectMapper); + objectJson.append(','); + objectJson.append(nextBDO.toJson(objectMapper)); counter++; } - objectJson += "]"; - return objectJson; + objectJson.append(']'); + return objectJson.toString(); } } diff --git a/src/main/java/com/percero/agents/sync/vo/PushCWUpdateResponse.java b/src/main/java/com/percero/agents/sync/vo/PushCWUpdateResponse.java index 981d5a1..f6ee78c 100644 --- a/src/main/java/com/percero/agents/sync/vo/PushCWUpdateResponse.java +++ b/src/main/java/com/percero/agents/sync/vo/PushCWUpdateResponse.java @@ -45,128 +45,128 @@ public void setValue(Object value) { @SuppressWarnings("rawtypes") public String toJson(ObjectMapper objectMapper) { - String objectJson = "{" + super.retrieveJson(objectMapper) + ",\"classIdPair\":"; + StringBuilder objectJson = new StringBuilder("{").append(super.retrieveJson(objectMapper)).append(",\"classIdPair\":"); if (getClassIdPair() != null) { - objectJson += getClassIdPair().toEmbeddedJson(); + objectJson.append(getClassIdPair().toEmbeddedJson()); } else { - objectJson += "null"; + objectJson.append("null"); } - objectJson += ",\"fieldName\":"; + objectJson.append(",\"fieldName\":"); if (getFieldName() == null) - objectJson += "null"; + objectJson.append("null"); else { if (objectMapper == null) objectMapper = new ObjectMapper(); try { - objectJson += objectMapper.writeValueAsString(getFieldName()); + objectJson.append(objectMapper.writeValueAsString(getFieldName())); } catch (JsonGenerationException e) { - objectJson += "null"; + objectJson.append("null"); e.printStackTrace(); } catch (JsonMappingException e) { - objectJson += "null"; + objectJson.append("null"); e.printStackTrace(); } catch (IOException e) { - objectJson += "null"; + objectJson.append("null"); e.printStackTrace(); } } - objectJson += ",\"params\":"; + objectJson.append(",\"params\":"); if (getParams() == null) - objectJson += "null"; + objectJson.append("null"); else { - objectJson += "["; + objectJson.append('['); if (objectMapper == null) objectMapper = new ObjectMapper(); int paramsCounter = 0; for(String nextParam : getParams()) { try { if (paramsCounter > 0) - objectJson += ","; - objectJson += objectMapper.writeValueAsString(nextParam); + objectJson.append(','); + objectJson.append(objectMapper.writeValueAsString(nextParam)); paramsCounter++; } catch (JsonGenerationException e) { - objectJson += "null"; + objectJson.append("null"); e.printStackTrace(); } catch (JsonMappingException e) { - objectJson += "null"; + objectJson.append("null"); e.printStackTrace(); } catch (IOException e) { - objectJson += "null"; + objectJson.append("null"); e.printStackTrace(); } } - objectJson += "]"; + objectJson.append(']'); } - objectJson += ",\"value\":"; + objectJson.append(",\"value\":"); if (value == null) { - objectJson += "null"; + objectJson.append("null"); } else { if (getValue() instanceof BaseDataObject) { - objectJson += ((BaseDataObject) getValue()).toEmbeddedJson(); + objectJson.append(((BaseDataObject) getValue()).toEmbeddedJson()); } else if (getValue() instanceof Collection) { Collection collection = (Collection) getValue(); if (objectMapper == null) objectMapper = new ObjectMapper(); - objectJson += "["; + objectJson.append('['); int collectionCounter = 0; for(Object nextCollectionObject : collection) { if (collectionCounter > 0) - objectJson += ","; + objectJson.append(','); if (nextCollectionObject instanceof BaseDataObject) { - objectJson += ((BaseDataObject) nextCollectionObject).toEmbeddedJson(); + objectJson.append(((BaseDataObject) nextCollectionObject).toEmbeddedJson()); } else { try { - objectJson += objectMapper.writeValueAsString(nextCollectionObject); + objectJson.append(objectMapper.writeValueAsString(nextCollectionObject)); } catch (JsonGenerationException e) { - objectJson += "null"; + objectJson.append("null"); e.printStackTrace(); } catch (JsonMappingException e) { - objectJson += "null"; + objectJson.append("null"); e.printStackTrace(); } catch (IOException e) { - objectJson += "null"; + objectJson.append("null"); e.printStackTrace(); } } collectionCounter++; } - objectJson += "]"; + objectJson.append(']'); } else { if (getValue() == null) - objectJson += "null"; + objectJson.append("null"); else { if (objectMapper == null) objectMapper = new ObjectMapper(); try { - objectJson += objectMapper.writeValueAsString(getValue()); + objectJson.append(objectMapper.writeValueAsString(getValue())); } catch (JsonGenerationException e) { - objectJson += "null"; + objectJson.append("null"); e.printStackTrace(); } catch (JsonMappingException e) { - objectJson += "null"; + objectJson.append("null"); e.printStackTrace(); } catch (IOException e) { - objectJson += "null"; + objectJson.append("null"); e.printStackTrace(); } } } } - objectJson += "}"; + objectJson.append('}'); - return objectJson; + return objectJson.toString(); } } diff --git a/src/main/java/com/percero/agents/sync/vo/PushDeleteResponse.java b/src/main/java/com/percero/agents/sync/vo/PushDeleteResponse.java index aaf6ae9..bf555ca 100644 --- a/src/main/java/com/percero/agents/sync/vo/PushDeleteResponse.java +++ b/src/main/java/com/percero/agents/sync/vo/PushDeleteResponse.java @@ -18,15 +18,15 @@ public void setObjectList(List value) { public String toJson(ObjectMapper objectMapper) { if (objectJson == null) { - String objectListJson = ""; + StringBuilder objectListJson = new StringBuilder(); int objectCounter = 0; for(RemovedClassIDPair nextObject : getObjectList()) { if (objectCounter > 0) - objectListJson += ","; - objectListJson += nextObject.toEmbeddedJson(); + objectListJson.append(','); + objectListJson.append(nextObject.toEmbeddedJson()); objectCounter++; } - return toJson(objectListJson, objectMapper); + return toJson(objectListJson.toString(), objectMapper); } else { return toJson(objectJson, objectMapper); @@ -39,8 +39,7 @@ public void setObjectJson(String value) { } public String toJson(String objectListJson, ObjectMapper objectMapper) { - String objectJson = "{" + super.retrieveJson(objectMapper) + ",\"objectList\":["; - objectJson += objectListJson + "]}"; + String objectJson = "{" + super.retrieveJson(objectMapper) + ",\"objectList\":[" + objectListJson + "]}"; return objectJson; } } diff --git a/src/main/java/com/percero/agents/sync/vo/PushObjectUpdate.java b/src/main/java/com/percero/agents/sync/vo/PushObjectUpdate.java index 4f4667c..a2a105a 100644 --- a/src/main/java/com/percero/agents/sync/vo/PushObjectUpdate.java +++ b/src/main/java/com/percero/agents/sync/vo/PushObjectUpdate.java @@ -22,31 +22,31 @@ public void setResult(BaseDataObject result) { @Override public String retrieveBaseJson(ObjectMapper objectMapper) { - String objectJson = super.retrieveBaseJson(objectMapper) + ","; - objectJson += "\"result\":" + (getResult() == null ? "null" : getResult().toJson(objectMapper)); + StringBuilder objectJson = new StringBuilder(super.retrieveBaseJson(objectMapper)).append(',') + .append("\"result\":").append((getResult() == null ? "null" : getResult().toJson(objectMapper))); - objectJson += ",\"fieldNames\":["; + objectJson.append(",\"fieldNames\":["); if (getFieldNames() != null && getFieldNames().length > 0) { if (objectMapper == null) objectMapper = new ObjectMapper(); for(int i=0; i < getFieldNames().length; i++) { try { - objectJson += objectMapper.writeValueAsString(getFieldNames()[i]); + objectJson.append(objectMapper.writeValueAsString(getFieldNames()[i])); } catch (JsonGenerationException e) { - objectJson += "null"; + objectJson.append("null"); e.printStackTrace(); } catch (JsonMappingException e) { - objectJson += "null"; + objectJson.append("null"); e.printStackTrace(); } catch (IOException e) { - objectJson += "null"; + objectJson.append("null"); e.printStackTrace(); } } } - objectJson += "]"; + objectJson.append(']'); - return objectJson; + return objectJson.toString(); } private String[] fieldNames; diff --git a/src/main/java/com/percero/agents/sync/vo/PushUpdateResponse.java b/src/main/java/com/percero/agents/sync/vo/PushUpdateResponse.java index e7025e6..bea0746 100644 --- a/src/main/java/com/percero/agents/sync/vo/PushUpdateResponse.java +++ b/src/main/java/com/percero/agents/sync/vo/PushUpdateResponse.java @@ -37,15 +37,15 @@ public void addUpdatedField(String fieldName) { public String toJson(ObjectMapper objectMapper) { if (objectJson == null) { - String objectListJson = ""; + StringBuilder objectListJson = new StringBuilder(); int objectCounter = 0; for(BaseDataObject nextObject : getObjectList()) { if (objectCounter > 0) - objectListJson += ","; - objectListJson += nextObject.toJson(); + objectListJson.append(','); + objectListJson.append(nextObject.toJson()); objectCounter++; } - return toJson(objectListJson, objectMapper); + return toJson(objectListJson.toString(), objectMapper); } else { return toJson(objectJson, objectMapper); @@ -61,8 +61,8 @@ protected String getObjectJson() { } public String toJson(String objectListJson, ObjectMapper objectMapper) { - String objectJson = "{" + super.retrieveJson(objectMapper) + ",\"objectList\":["; - objectJson += objectListJson + "],\"updatedFields\":["; + StringBuilder objectJson = new StringBuilder("{").append(super.retrieveJson(objectMapper)).append(",\"objectList\":[") + .append(objectListJson).append("],\"updatedFields\":["); int partialsCounter = 0; if (getUpdatedFields() != null) { @@ -70,19 +70,19 @@ public String toJson(String objectListJson, ObjectMapper objectMapper) { while (itrUpdatedFields.hasNext()) { String nextField = itrUpdatedFields.next(); if (partialsCounter > 0) - objectJson += ","; - objectJson += "\"" + nextField + "\""; // Because this is a field name we shouldn't need any special string transforms. + objectJson.append(','); + objectJson.append('"').append(nextField).append('"'); // Because this is a field name we shouldn't need any special string transforms. partialsCounter++; } } else { - objectJson += "null"; + objectJson.append("null"); } - objectJson += "]"; + objectJson.append(']'); - objectJson += "}"; - return objectJson; + objectJson.append('}'); + return objectJson.toString(); } } diff --git a/src/main/java/com/percero/agents/sync/vo/SearchByExampleResponse.java b/src/main/java/com/percero/agents/sync/vo/SearchByExampleResponse.java index 0308726..7b1a876 100644 --- a/src/main/java/com/percero/agents/sync/vo/SearchByExampleResponse.java +++ b/src/main/java/com/percero/agents/sync/vo/SearchByExampleResponse.java @@ -17,15 +17,15 @@ public void setResult(List result) { @Override public String retrieveBaseJson(ObjectMapper objectMapper) { - String objectJson = super.retrieveBaseJson(objectMapper) + ",\"result\":["; + StringBuilder objectJson = new StringBuilder(super.retrieveBaseJson(objectMapper)).append(",\"result\":["); int counter = 0; for(BaseDataObject nextBDO : result) { if (counter > 0) - objectJson += ","; - objectJson += nextBDO.toJson(objectMapper); + objectJson.append(','); + objectJson.append(nextBDO.toJson(objectMapper)); counter++; } - objectJson += "]"; - return objectJson; + objectJson.append(']'); + return objectJson.toString(); } } diff --git a/src/main/java/com/percero/agents/sync/vo/SyncResponse.java b/src/main/java/com/percero/agents/sync/vo/SyncResponse.java index 742f2ef..1967077 100644 --- a/src/main/java/com/percero/agents/sync/vo/SyncResponse.java +++ b/src/main/java/com/percero/agents/sync/vo/SyncResponse.java @@ -100,47 +100,69 @@ public String retrieveJson(ObjectMapper objectMapper) { } public String retrieveBaseJson(ObjectMapper objectMapper) { - String objectJson = "\"cn\":\"" + getClass().getCanonicalName() + "\"," + - "\"clientId\":" + (getClientId() == null ? "null" : "\"" + getClientId() + "\"") + "," + - "\"timestamp\":" + getTimestamp().toString() + ","; + StringBuilder objectJson = new StringBuilder("\"cn\":\"").append(getClass().getCanonicalName()).append("\",") + .append("\"clientId\":"); + + if (getClientId() == null) { + objectJson.append("null"); + } + else { + objectJson.append('"').append(getClientId()).append('"'); + } + + objectJson.append(',').append("\"timestamp\":").append(getTimestamp().toString()).append(','); if (getIds() != null) { - objectJson += "\"ids\":["; + objectJson.append("\"ids\":["); int idsCounter = 0; for(String nextId : getIds()) { if (idsCounter > 0) - objectJson += ","; - objectJson += "\"" + nextId + "\""; + objectJson.append(','); + objectJson.append('"').append(nextId).append('"'); idsCounter++; } - objectJson += "],"; + objectJson.append("],"); } else { - objectJson += "\"ids\":null,"; + objectJson.append("\"ids\":null,"); } try { if (objectMapper == null) objectMapper = new ObjectMapper(); - objectJson += "\"data\":" + (getData() == null ? "null" : objectMapper.writeValueAsString(getData())) + ","; + objectJson.append("\"data\":").append((getData() == null ? "null" : objectMapper.writeValueAsString(getData()))).append(','); } catch (JsonGenerationException e) { // TODO Auto-generated catch block e.printStackTrace(); - objectJson += "\"data\":null"; + objectJson.append("\"data\":null"); } catch (JsonMappingException e) { // TODO Auto-generated catch block e.printStackTrace(); - objectJson += "\"data\":null"; + objectJson.append("\"data\":null"); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); - objectJson += "\"data\":null"; + objectJson.append("\"data\":null"); } - objectJson += "\"type\":" + (getType() == null ? "null" : "\"" + getType() + "\"") + "," + - "\"correspondingMessageId\":" + (getCorrespondingMessageId() == null ? "null" : "\"" + getCorrespondingMessageId() + "\"") + "," + - "\"gatewayMessageId\":" + (getGatewayMessageId() == null ? "null" : "\"" + getGatewayMessageId() + "\""); + objectJson.append("\"type\":").append((getType() == null ? "null" : "\"" + getType() + "\"")).append(',') + .append("\"correspondingMessageId\":"); - return objectJson; + if (getCorrespondingMessageId() == null) { + objectJson.append("null"); + } + else { + objectJson.append('"').append(getCorrespondingMessageId()).append('"'); + } + objectJson.append(',').append("\"gatewayMessageId\":"); + + if (getGatewayMessageId() == null) { + objectJson.append("null"); + } + else { + objectJson.append('"').append(getGatewayMessageId()).append('"'); + } + + return objectJson.toString(); } } diff --git a/src/main/java/com/percero/amqp/PerceroAgentListener.java b/src/main/java/com/percero/amqp/PerceroAgentListener.java index a5f2122..30cba91 100644 --- a/src/main/java/com/percero/amqp/PerceroAgentListener.java +++ b/src/main/java/com/percero/amqp/PerceroAgentListener.java @@ -13,6 +13,10 @@ import com.percero.agents.sync.vo.SyncRequest; import com.percero.amqp.handlers.*; import com.percero.framework.accessor.IAccessorService; + +import java.util.HashSet; +import java.util.Set; + import org.apache.log4j.Logger; import org.springframework.amqp.core.AmqpAdmin; import org.springframework.amqp.core.AmqpTemplate; @@ -26,6 +30,7 @@ import org.springframework.core.task.TaskExecutor; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; /** * This class supplies the main method that creates the spring context @@ -145,7 +150,13 @@ else if (ob instanceof AuthRequest) { handleAuthRequest((AuthRequest) ob, key, replyTo); } } catch (Exception e) { - logger.error("Unable to process message", e); + try { + // Attempt to print out the unrecognizable message. + logger.error("Unable to process message: " + new String(message.getBody()), e); + } catch(Exception e1) { + // Else just report the exception. + logger.error("Unable to process message", e); + } } } @@ -288,6 +299,11 @@ else if(key.equals("reauthenticate") && request instanceof ReauthenticationReque response = authService2.reauthenticate(authRequest); result = response; } + else if(key.equals("register") && request instanceof AuthenticationRequest){ + AuthenticationRequest authRequest = (AuthenticationRequest) request; + response = authService2.register(authRequest); + result = response; + } /** Essentially, Re-login **/ else if(key.equals(ValidateUserByTokenRequest.ID) || key.equals("validateUserByToken")){ if (request instanceof ValidateUserByTokenRequest) { @@ -356,7 +372,15 @@ else if(key.equals(AuthenticateOAuthAccessTokenRequest.ID) || key.equals("authen else if(key.equals("disconnectAuth")){ if (request instanceof com.percero.agents.auth.vo.DisconnectRequest) { response = new DisconnectResponse(); - result = authService.logoutUser(request.getUserId(), request.getToken(), request.getClientId()); + Set clientIds = new HashSet(); + clientIds.add(request.getClientId()); + if (StringUtils.hasText(((com.percero.agents.auth.vo.DisconnectRequest) request).getExistingClientId())) { + clientIds.add(((com.percero.agents.auth.vo.DisconnectRequest) request).getExistingClientId()); + } + if (((com.percero.agents.auth.vo.DisconnectRequest) request).getExistingClientIds() != null && !((com.percero.agents.auth.vo.DisconnectRequest) request).getExistingClientIds().isEmpty()) { + clientIds.addAll(((com.percero.agents.auth.vo.DisconnectRequest) request).getExistingClientIds()); + } + result = authService.logoutUser(request.getUserId(), request.getToken(), clientIds); ((DisconnectResponse) response).setResult((Boolean)result); } } diff --git a/src/main/java/com/percero/amqp/QueueProperties.java b/src/main/java/com/percero/amqp/QueueProperties.java new file mode 100644 index 0000000..4721744 --- /dev/null +++ b/src/main/java/com/percero/amqp/QueueProperties.java @@ -0,0 +1,54 @@ +package com.percero.amqp; + +import org.joda.time.Instant; + +/** + * Holds basic information about a Message Queue, such as name and number of + * messages. + * + * @author Collin Brown + * + */ +public class QueueProperties { + + String queueName = null; + int numMessages = 0; + int numConsumers = 0; + Instant dateTimeIdleSince = null; + boolean isEolClientsMember = false; + + public String getQueueName() { + return queueName; + } + public void setQueueName(String queueName) { + this.queueName = queueName; + } + public int getNumMessages() { + return numMessages; + } + public void setNumMessages(int numMessages) { + this.numMessages = numMessages; + } + public int getNumConsumers() { + return numConsumers; + } + public void setNumConsumers(int numConsumers) { + this.numConsumers = numConsumers; + } + public Instant getDateTimeIdleSince() { + return dateTimeIdleSince; + } + public void setDateTimeIdleSince(Instant dateTimeIdleSince) { + this.dateTimeIdleSince = dateTimeIdleSince; + } + public boolean isEolClientsMember() { + return isEolClientsMember; + } + public void setEolClientsMember(boolean isEolClientsMember) { + this.isEolClientsMember = isEolClientsMember; + } + public QueueProperties() { + // TODO Auto-generated constructor stub + } + +} diff --git a/src/main/java/com/percero/amqp/RabbitMQPushSyncHelper.java b/src/main/java/com/percero/amqp/RabbitMQPushSyncHelper.java index 2d6a306..42c80e3 100644 --- a/src/main/java/com/percero/amqp/RabbitMQPushSyncHelper.java +++ b/src/main/java/com/percero/amqp/RabbitMQPushSyncHelper.java @@ -8,6 +8,7 @@ import java.util.Collection; import java.util.HashSet; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Set; @@ -16,6 +17,7 @@ import org.apache.commons.io.IOUtils; import org.apache.http.HttpResponse; import org.apache.http.auth.AuthScope; +import org.apache.http.auth.Credentials; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.HttpGet; @@ -24,6 +26,9 @@ import org.codehaus.jackson.JsonNode; import org.codehaus.jackson.map.ObjectMapper; import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; +import org.joda.time.Instant; +import org.joda.time.MutableDateTime; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import org.springframework.amqp.AmqpIOException; @@ -43,28 +48,28 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import com.google.gson.JsonSyntaxException; import com.percero.agents.sync.access.IAccessManager; import com.percero.agents.sync.access.RedisKeyUtils; +import com.percero.agents.sync.cw.CheckChangeWatcherMessage; import com.percero.agents.sync.datastore.ICacheDataStore; import com.percero.agents.sync.services.IPushSyncHelper; import com.percero.agents.sync.vo.BaseDataObject; +import com.percero.agents.sync.vo.ClassIDPair; import com.percero.agents.sync.vo.IJsonObject; import com.percero.agents.sync.vo.PushUpdateResponse; import com.percero.agents.sync.vo.SyncResponse; +import com.percero.framework.vo.IPerceroObject; import com.rabbitmq.client.ShutdownSignalException; import edu.emory.mathcs.backport.java.util.Arrays; -import org.slf4j.*; - -@Component public class RabbitMQPushSyncHelper implements IPushSyncHelper, ApplicationContextAware { private static Logger logger = Logger.getLogger(RabbitMQPushSyncHelper.class); @@ -98,16 +103,16 @@ public void setRabbitMessageListenerContainer(AbstractMessageListenerContainer c } // RabbitMQ environment variables. - @Autowired @Value("$pf{gateway.rabbitmq.admin_port:15672}") - int rabbitAdminPort = 15672; - @Autowired @Value("$pf{gateway.rabbitmq.login:guest}") - String rabbitLogin = "guest"; - @Autowired @Value("$pf{gateway.rabbitmq.password:guest}") - String rabbitPassword = "guest"; - @Autowired @Value("$pf{gateway.rabbitmq.host:localhost}") - String rabbitHost = null; - @Autowired @Value("$pf{gateway.rabbitmq.queue_timeout:43200000}") // 8 Hours - long rabbitQueueTimeout = 43200000; + @Value("$pf{gateway.rabbitmq.admin_port:15672}") + int rabbitAdminPort; + @Value("$pf{gateway.rabbitmq.login:guest}") + String rabbitLogin; + @Value("$pf{gateway.rabbitmq.password:guest}") + String rabbitPassword; + @Value("$pf{gateway.rabbitmq.host:localhost}") + String rabbitHost; + @Value("$pf{gateway.rabbitmq.queue_timeout:43200000}") // 8 Hours + long rabbitQueueTimeout; @Autowired ICacheDataStore cacheDataStore; @@ -117,37 +122,31 @@ public void setCacheDataStore(ICacheDataStore cacheDataStore) { @SuppressWarnings("rawtypes") protected void pushJsonToRouting(String objectJson, Class objectClass, String routingKey) { - try{ - - + try { Message convertedMessage = toMessage(objectJson, objectClass, MessageProperties.CONTENT_TYPE_JSON); template.send(routingKey, convertedMessage); } - catch(Exception e){ + catch(Exception e) { logger.error(e.getMessage(), e); } } protected void pushMessageToRouting(Message convertedMessage, String routingKey) { - try{ - - + try { template.send(routingKey, convertedMessage); } - catch(Exception e){ + catch(Exception e) { logger.error(e.getMessage(), e); } } @SuppressWarnings("rawtypes") protected void pushStringToRouting(String objectJson, Class objectClass, String routingKey) { - try{ - - + try { Message convertedMessage = toMessage(objectJson, objectClass, MessageProperties.CONTENT_TYPE_BYTES); template.send(routingKey, convertedMessage); } - catch(Exception e){ + catch(Exception e) { logger.error(e.getMessage(), e); } } @@ -155,8 +154,6 @@ protected void pushStringToRouting(String objectJson, Class objectClass, String @SuppressWarnings("rawtypes") public final Message toMessage(String objectJson, Class objectClass, String contentEncoding) throws MessageConversionException { - - MessageProperties messageProperties = new MessageProperties(); messageProperties.setContentType(MessageProperties.CONTENT_TYPE_JSON); messageProperties.setContentEncoding(this.defaultCharset); @@ -167,8 +164,6 @@ public final Message toMessage(String objectJson, Class objectClass, String cont @SuppressWarnings("rawtypes") public final Message toMessage(String objectJson, Class objectClass, MessageProperties messageProperties, String contentEncoding) throws MessageConversionException { - - Message message = createMessage(objectJson, objectClass, messageProperties, contentEncoding); return message; } @@ -176,9 +171,6 @@ public final Message toMessage(String objectJson, Class objectClass, MessageProp @SuppressWarnings("rawtypes") protected Message createMessage(String aString, Class objectClass, MessageProperties messageProperties, String contentEncoding) throws MessageConversionException { - - - byte[] bytes = null; try { String jsonString = aString; @@ -190,15 +182,11 @@ protected Message createMessage(String aString, Class objectClass, MessageProper messageProperties.setContentLength(bytes.length); } -// String objectClassName = objectClass.getName(); -// messageProperties.getHeaders().put("__TypeId__", objectClassName); classMapper.fromClass(objectClass, messageProperties); return new Message(bytes, messageProperties); } public void pushSyncResponseToClient(SyncResponse anObject, String clientId) { - - if (anObject != null && StringUtils.hasText(clientId)) { pushJsonToRouting(anObject.toJson(objectMapper), anObject.getClass(), clientId); } @@ -206,8 +194,6 @@ public void pushSyncResponseToClient(SyncResponse anObject, String clientId) { @SuppressWarnings("rawtypes") public void pushSyncResponseToClients(SyncResponse syncResponse, Collection clientIds) { - - if ( syncResponse != null && clientIds != null && !clientIds.isEmpty() ) { Class objectClass = syncResponse.getClass(); @@ -222,8 +208,6 @@ public void pushSyncResponseToClients(SyncResponse syncResponse, Collection listClients) { - - if (anObject != null && listClients != null && !listClients.isEmpty() ) { // Route to specific clients. // Optimization: create the JSON string of the object. @@ -247,17 +231,29 @@ public void pushObjectToClients(Object anObject, Collection listClients) @Override public void pushStringToRoute(String aString, String routeName) { - - if (StringUtils.hasText(routeName)) { pushStringToRouting(aString, String.class, routeName); } } + @Override + public void enqueueCheckChangeWatcher(ClassIDPair classIDPair, String[] fieldNames, String[] params){ + enqueueCheckChangeWatcher(classIDPair, fieldNames, params, null); + } + + @Override + public void enqueueCheckChangeWatcher(ClassIDPair classIDPair, String[] fieldNames, String[] params, IPerceroObject oldValue){ + CheckChangeWatcherMessage message = new CheckChangeWatcherMessage(); + message.classIDPair = classIDPair; + message.fieldNames = fieldNames; + message.params = params; + if(oldValue != null) + message.oldValueJson = ((BaseDataObject)oldValue).toJson(); + template.convertAndSend("checkChangeWatcher", message); + } + @Override public Boolean removeClient(String clientId) { - - try { if (!cacheDataStore.getSetIsMember(RedisKeyUtils.eolClients(), clientId)) { logger.debug("RabbitMQ Removing Client " + clientId); @@ -296,8 +292,6 @@ public Boolean removeClient(String clientId) { } protected Boolean deleteQueue(String queue) { - - try { logger.debug("RabbitMQ Deleting Queue " + queue); Queue clientQueue = new Queue(queue, durableQueues); @@ -317,8 +311,6 @@ protected Boolean deleteQueue(String queue) { @Override public Boolean renameClient(String thePreviousClientId, String clientId) { - - if (!StringUtils.hasText(thePreviousClientId)) { logger.warn("RabbitMQ renameClient previous client not set"); return false; @@ -385,176 +377,319 @@ else if (clientId.equalsIgnoreCase(thePreviousClientId)) { // SCHEDULED TASKS ////////////////////////////////////////////////// - private Boolean validatingQueues = false; + Boolean validatingQueues = false; // @Scheduled(fixedRate=600000) // 10 Minutes // @Scheduled(fixedRate=30000) // 30 Seconds @Scheduled(fixedRate=300000) // 5 Minutes - public void validateQueues() { + public void runValidateQueues() { + validateQueues(); + } + + public boolean validateQueues() { - synchronized (validatingQueues) { if (validatingQueues) { // Currently running. - return; + return false; } else { validatingQueues = true; } } - String host = rabbitHost; - if (!StringUtils.hasText(host)) { - // No Rabbit host configured? Very strange, but no sense in moving forward here... - logger.error("No RabbitMQ host configured?"); - return; + try { + Set queuesToCheck = retrieveQueueProperties(false); // We only want NON system queues. + Set queueNamesToCheck = new HashSet(); + + for(QueueProperties queueProperties : queuesToCheck) { + if (checkQueueForDeletion(queueProperties)) { + deleteQueue(queueProperties.queueName); + } + else if (checkQueueForLogout(queueProperties)) { + // This queue did not pass the test to be deleted, add + // it to the list of valid client queue names to check + // for logout. + queueNamesToCheck.add(queueProperties.queueName); + } + } + + // Check to see if each queue name is a valid client. + if (!queueNamesToCheck.isEmpty()) { + Set validClients = accessManager.validateClients(queueNamesToCheck); + // Remove all valid clients from the queue names. + queueNamesToCheck.removeAll(validClients); + + // Now delete the remaining INVALID queues. + Iterator itrQueuesToDelete = queueNamesToCheck.iterator(); + while (itrQueuesToDelete.hasNext()) { + String nextQueueName = itrQueuesToDelete.next(); + logger.debug("RabbitMQ Logging out client " + nextQueueName); + accessManager.logoutClient(nextQueueName, true); + } + } + + } catch (ClientProtocolException e) { + logger.debug(e); + } catch (IOException e) { + logger.debug(e); + } catch (Exception e) { + logger.warn(e); + } finally { + synchronized (validatingQueues) { + validatingQueues = false; + } } - String uri = "http://" + host + ":" + rabbitAdminPort + "/api/queues/"; + // Loop through EOL queues and delete any that now have no clients. - DefaultHttpClient httpClient = new DefaultHttpClient(); - httpClient.getCredentialsProvider().setCredentials(new AuthScope(host, rabbitAdminPort), new UsernamePasswordCredentials(rabbitLogin, rabbitPassword)); - HttpGet httpGet = new HttpGet(uri); + return true; + } + + protected Set retrieveQueueProperties(boolean includeSystemQueues) throws JsonSyntaxException, ClientProtocolException, IOException { + Set queuesToCheck = new HashSet(); - try { - HttpResponse r = httpClient.execute(httpGet); - StringWriter writer = new StringWriter(); - InputStream is = r.getEntity().getContent(); - String encoding = null; - if (r.getEntity().getContentEncoding() != null) { - encoding = r.getEntity().getContentEncoding().getValue(); - IOUtils.copy(is, writer, encoding); - } - else { - IOUtils.copy(is, writer); - } - String theString = writer.toString(); - + JsonParser parser = new JsonParser(); + JsonElement jsonQueues = parser.parse(retrieveQueuesJsonListAsString()); + JsonArray jsonQueuesArray = jsonQueues.getAsJsonArray(); + + if (jsonQueuesArray != null) { int numQueues = 0; - Set queueNamesToCheck = null; - - JsonParser parser = new JsonParser(); - JsonElement jsonQueues = parser.parse(theString); - JsonArray jsonQueuesArray = jsonQueues.getAsJsonArray(); + numQueues = jsonQueuesArray.size(); + logger.debug("Found " + numQueues + " RabbitMQ Queues to validate..."); - if (jsonQueuesArray != null) { - numQueues = jsonQueuesArray.size(); - logger.debug("Found " + numQueues + " RabbitMQ Queues to validate..."); - queueNamesToCheck = new HashSet(numQueues - queueNames.size()); + Iterator itrJsonQueuesArray = jsonQueuesArray.iterator(); + while (itrJsonQueuesArray.hasNext()) { + JsonElement nextJsonQueue = itrJsonQueuesArray.next(); + JsonObject nextJsonQueueObject = nextJsonQueue.getAsJsonObject(); + + QueueProperties queueProperties = new QueueProperties(); - Iterator itrJsonQueuesArray = jsonQueuesArray.iterator(); - while (itrJsonQueuesArray.hasNext()) { - JsonElement nextJsonQueue = itrJsonQueuesArray.next(); - JsonObject nextJsonQueueObject = nextJsonQueue.getAsJsonObject(); - - JsonElement nextJsonQueueName = nextJsonQueueObject.get("name"); - String nextQueueName = null; - if (nextJsonQueueName != null) { - nextQueueName = nextJsonQueueName.getAsString(); - } - else { + JsonElement nextJsonQueueName = nextJsonQueueObject.get("name"); + if (nextJsonQueueName != null) { + queueProperties.queueName = nextJsonQueueName.getAsString(); + if (!StringUtils.hasText(queueProperties.queueName) || (!includeSystemQueues && queueNames.contains(queueProperties.queueName))) { + // No name OR System Queue -> Ignore continue; } + } + else { + // No queue name, so we can't really do much... + continue; + } - if (cacheDataStore.getSetIsMember(RedisKeyUtils.eolClients(), nextQueueName)) { - JsonElement nextJsonQueueMessages = nextJsonQueueObject.get("messages"); - int nextQueueMessages = 0; - if (nextJsonQueueMessages != null) { - nextQueueMessages = nextJsonQueueMessages.getAsInt(); - - if (nextQueueMessages <= 0) { - logger.debug("Deleting EOL empty queue " + nextQueueName); - deleteQueue(nextQueueName); - continue; - } - } - } - - JsonElement nextJsonQueueConsumers = nextJsonQueueObject.get("consumers"); - if (nextJsonQueueConsumers != null) { - // If the queue has consumers, then leave it alone. - int numConsumers = nextJsonQueueConsumers.getAsInt(); - if (numConsumers == 0) { - // If this queue is in the EOL list, then it can simply be deleted. - if (cacheDataStore.getSetIsMember(RedisKeyUtils.eolClients(), nextQueueName)) { - logger.debug("Deleting EOL no consumers queue " + nextQueueName); - deleteQueue(nextQueueName); - continue; - } - } - else { - // Queue has consumers, so leave alone for now. - continue; + JsonElement nextJsonQueueMessages = nextJsonQueueObject.get("messages"); + if (nextJsonQueueMessages != null) { + queueProperties.numMessages = nextJsonQueueMessages.getAsInt(); + } + + JsonElement nextJsonQueueConsumers = nextJsonQueueObject.get("consumers"); + if (nextJsonQueueConsumers != null) { + // If the queue has consumers, then leave it alone. + queueProperties.numConsumers = nextJsonQueueConsumers.getAsInt(); + } + + JsonElement nextJsonQueueIdleSince = nextJsonQueueObject.get("idle_since"); + if (nextJsonQueueIdleSince != null) { + try { + String strIdleSince = nextJsonQueueIdleSince.getAsString(); + DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss"); + MutableDateTime dateTime = formatter.withOffsetParsed().parseMutableDateTime(strIdleSince); + if (dateTime != null) { + dateTime.setZoneRetainFields(DateTimeZone.UTC); + queueProperties.dateTimeIdleSince = dateTime.toInstant(); } + } catch(Exception e) { + logger.debug("Error getting idle since for queue " + queueProperties.queueName, e); } - - JsonElement nextJsonQueueIdleSince = nextJsonQueueObject.get("idle_since"); - if (nextJsonQueueIdleSince != null) { - try { - String strIdleSince = nextJsonQueueIdleSince.getAsString(); - DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss"); - DateTime dateTime = formatter.withOffsetParsed().parseDateTime(strIdleSince); - if (dateTime != null) { - DateTime currentDateTime = new DateTime(System.currentTimeMillis()); - currentDateTime = currentDateTime.toDateTime(dateTime.getZone()); - long timeDiffInMs = currentDateTime.toDate().getTime() - dateTime.toDate().getTime(); - if (timeDiffInMs < rabbitQueueTimeout) { - // Queue has NOT timed out yet. - continue; - } - } - } catch(Exception e) { - // Do nothing - logger.debug("Error getting idle since for queue " + nextQueueName, e); - continue; - } + } + else { + logger.debug("Unable to determine idle since time, ignoring queue " + queueProperties.queueName); + } + + queueProperties.isEolClientsMember = cacheDataStore.getSetIsMember(RedisKeyUtils.eolClients(), queueProperties.queueName); + + queuesToCheck.add(queueProperties); + } + } + + return queuesToCheck; + } + + protected String retrieveQueuesJsonListAsString() throws ClientProtocolException, IOException { + String host = rabbitHost; + if (!StringUtils.hasText(host)) { + // No Rabbit host configured? Very strange, but no sense in moving forward here... + logger.error("No RabbitMQ host configured?"); + return ""; + } + + String uri = "http://" + host + ":" + rabbitAdminPort + "/api/queues/"; + return issueHttpCall(uri, new AuthScope(host, rabbitAdminPort), new UsernamePasswordCredentials(rabbitLogin, rabbitPassword)); + } + + protected String issueHttpCall(String uri, AuthScope authScope, Credentials credentials) throws ClientProtocolException, IOException { + DefaultHttpClient httpClient = new DefaultHttpClient(); + httpClient.getCredentialsProvider().setCredentials(authScope , credentials); + HttpGet httpGet = new HttpGet(uri); + + HttpResponse r = httpClient.execute(httpGet); + StringWriter writer = new StringWriter(); + InputStream is = r.getEntity().getContent(); + String encoding = null; + if (r.getEntity().getContentEncoding() != null) { + encoding = r.getEntity().getContentEncoding().getValue(); + IOUtils.copy(is, writer, encoding); + } + else { + IOUtils.copy(is, writer); + } + return writer.toString(); + } + + protected boolean checkQueueForDeletion(QueueProperties queueProperties) { + return checkQueueForDeletion(queueProperties.queueName, queueProperties.numMessages, queueProperties.numConsumers, queueProperties.dateTimeIdleSince, queueProperties.isEolClientsMember); + } + + /** + * For each existing queue, the queue can be deleted when the following are true: + * 1. The queue is NOT a system queue {@link #queueNames} + * 1. The queue is in the list of EOL Clients {@link RedisKeyUtils#eolClients()} AND + * 1. There are NO messages left in the queue + * OR + * 2. There are NO consumers of the queue + * OR + * 2. The queue has been idle for at least {@link #rabbitQueueTimeout} milliseconds AND + * the queue has no consumers and has contains an EOL message + * In this case, we are assuming that this is a queue for a client that has gone away + * and is never coming back. This timeout should be in the number of days/weeks range. + * + * @param queueName + * @param numMessages + * @param numConsumers + * @param dateTimeIdleSince + * @param isEolClientsMember + * @return + */ + protected boolean checkQueueForDeletion(String queueName, int numMessages, int numConsumers, Instant dateTimeIdleSince, boolean isEolClientsMember) { + if (queueNames.contains(queueName)) { + return false; + } + + if (isEolClientsMember && (numMessages == 0 || numConsumers == 0)) { + logger.debug("Deleting EOL empty queue " + queueName + ": EOL Client with " + numMessages + " Messages and " + numConsumers + " Consumers"); + return true; + } + + if (numConsumers == 0 && numMessages > 0 && queueHasTimedOut(queueName, dateTimeIdleSince)) { + // If this queue has an EOL message and has no + // consumers, then we can safely delete it. + // If we have gotten here then client is NOT in + // the EOL list, thus it is old and can safely + // be deleted. + Message nextExistingMessage = null; + + // If we find an EOL Message, + Message eolMessage = null; + List messagesToRequeue = new ArrayList<>(); + while ((nextExistingMessage = template.receive(queueName)) != null) { + try { + JsonNode messageJsonNode = objectMapper.readTree(nextExistingMessage.getBody()); + if (messageJsonNode.has("EOL") && messageJsonNode.get("EOL").getBooleanValue()) { + // We found an EOL message -> This queue to be deleted. + eolMessage = nextExistingMessage; + break; } else { - logger.debug("Unable to determine idle since time, ignoring queue " + nextQueueName); - continue; - } - - if (StringUtils.hasText(nextQueueName)) { - // Check to see if this queue still valid. - if (!queueNames.contains(nextQueueName)) { - // Valid Queue, used by system. - queueNamesToCheck.add(nextQueueName); - } + // Re-queue this message... + messagesToRequeue.add(nextExistingMessage); } + } catch (IOException e) { + logger.warn("Error reading queue " + queueName + " message, unable to process"); } - - // Check to see if each queue name is a valid client. - if (!queueNamesToCheck.isEmpty()) { - Set validClients = accessManager.validateClients(queueNamesToCheck); - // Remove all valid clients from the queue names. - queueNamesToCheck.removeAll(validClients); - - // Now delete the remaining INVALID queues. - Iterator itrQueuesToDelete = queueNamesToCheck.iterator(); - while (itrQueuesToDelete.hasNext()) { - String nextQueueName = itrQueuesToDelete.next(); - logger.debug("RabbitMQ Logging out client " + nextQueueName); - accessManager.logoutClient(nextQueueName, true); - } + } + + if (eolMessage != null) { + logger.debug("Deleting EOL no consumers queue " + queueName + ": EOL Client WITH EOL Message"); + return true; + } + else { + // Need to re-queue the messages. + for(Message nextMessage : messagesToRequeue) { + template.send(queueName, nextMessage); } } + } - } catch (ClientProtocolException e) { - logger.debug(e); - } catch (IOException e) { - logger.debug(e); - } catch (Exception e) { - logger.warn(e); - } finally { - synchronized (validatingQueues) { - validatingQueues = false; + return false; + } + + protected boolean checkQueueForLogout(QueueProperties queueProperties) { + return checkQueueForLogout(queueProperties.queueName, queueProperties.numMessages, queueProperties.numConsumers, queueProperties.dateTimeIdleSince, queueProperties.isEolClientsMember); + } + + /** + * If a queue meets this criteria, then it should be further investigated for automatic logout. + * 1. The queue is NOT a system queue {@link #queueNames} + * 2. Is NOT a member of EOL Clients list + * 3. Has NO consumers + * 4. The queue has been idle for at least {@link #rabbitQueueTimeout} milliseconds + * In this case, we are assuming that this is a queue for a client that has gone away + * and is never coming back. This timeout should be in the number of days/weeks range. + * + * @param queueName + * @param numMessages + * @param numConsumers + * @param dateTimeIdleSince + * @param isEolClientsMember + * @return + */ + protected boolean checkQueueForLogout(String queueName, int numMessages, int numConsumers, Instant dateTimeIdleSince, boolean isEolClientsMember) { + if (queueNames.contains(queueName)) { + return false; + } + + if (!isEolClientsMember) { + if (numConsumers <= 0) { + if (queueHasTimedOut(queueName, dateTimeIdleSince)) { + // Queue HAS timed out. + logger.debug("Queue Timed Out: " + queueName); + return true; + } } } - // Loop through EOL queues and delete any that now have no clients. - + return false; } - private Collection queueNames = null; + /** + * If the queue has been idle for at least {@link #rabbitQueueTimeout} milliseconds then returns true. + * + * @param queueName + * @param dateTimeIdleSince + * @return + */ + protected boolean queueHasTimedOut(String queueName, Instant dateTimeIdleSince) { + if (queueNames.contains(queueName)) { + // System queues can NEVER timeout. + return false; + } + + boolean queueHasTimedOut = false; + if (dateTimeIdleSince != null) { + DateTime currentDateTime = new DateTime(System.currentTimeMillis()); + currentDateTime = currentDateTime.toDateTime(dateTimeIdleSince.getZone()); + long timeDiffInMs = currentDateTime.toDate().getTime() - dateTimeIdleSince.toDate().getTime(); + if (timeDiffInMs > rabbitQueueTimeout) { + // Queue HAS timed out. + logger.debug("Queue Timed Out: " + queueName); + + queueHasTimedOut = true; + } + } + return queueHasTimedOut; + } + + Collection queueNames = null; private ApplicationContext applicationContext = null; @SuppressWarnings("unchecked") @@ -566,15 +701,14 @@ public void setApplicationContext(ApplicationContext applicationContext) Map queues = this.applicationContext.getBeansOfType(Queue.class); // Make sure these queue names are protected. - String[] strSaveQueueNames = {"authenticateOAuthCode", "42", "authenticateOAuthAccessToken", "41", - "authenticateUserAccount", "getServiceUsers", "getOAuthRequestToken", - "getRegAppOAuths", "getRegisteredApplication", "getAllServiceProviders", - "logoutUser", "testCall", "validateUserByToken", "17", "disconnectAuth", - "reconnect", "connect", "hibernate", "upgradeClient", "disconnect", "logout", "create", - "update", "processTransaction", "getChangeWatcher", "findById", "findByIds", - "findByExample", "countAllByName", "getAllByName", "runQuery", "runProcess", - "createObject", "putObject", "removeObject", "updatesReceived", "deletesReceived", - "searchByExample", "delete", "getAccessor", "getHistory", "changeWatcher"}; + String[] strSaveQueueNames = { "authenticateOAuthCode", "42", "register", "authenticate", "reauthenticate", + "authenticateOAuthAccessToken", "41", "authenticateUserAccount", "getServiceUsers", + "getOAuthRequestToken", "getRegAppOAuths", "getRegisteredApplication", "getAllServiceProviders", + "logoutUser", "testCall", "validateUserByToken", "17", "disconnectAuth", "reconnect", "connect", + "hibernate", "upgradeClient", "disconnect", "logout", "create", "update", "processTransaction", + "getChangeWatcher", "findById", "findByIds", "findByExample", "countAllByName", "getAllByName", + "runQuery", "runProcess", "createObject", "putObject", "removeObject", "updatesReceived", + "deletesReceived", "searchByExample", "delete", "getAccessor", "getHistory", "changeWatcher" }; queueNames = new HashSet(queues.size()); queueNames.addAll( Arrays.asList(strSaveQueueNames) ); @@ -585,5 +719,5 @@ public void setApplicationContext(ApplicationContext applicationContext) queueNames.add(nextQueue.getName()); } } - + } \ No newline at end of file diff --git a/src/main/java/com/percero/amqp/handlers/FindByExampleHandler.java b/src/main/java/com/percero/amqp/handlers/FindByExampleHandler.java index d283d95..738235c 100644 --- a/src/main/java/com/percero/amqp/handlers/FindByExampleHandler.java +++ b/src/main/java/com/percero/amqp/handlers/FindByExampleHandler.java @@ -1,10 +1,13 @@ package com.percero.amqp.handlers; +import java.util.ArrayList; import java.util.List; import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; import com.percero.agents.sync.vo.BaseDataObject; +import com.percero.agents.sync.vo.ClassIDPair; import com.percero.agents.sync.vo.FindByExampleRequest; import com.percero.agents.sync.vo.FindByExampleResponse; import com.percero.agents.sync.vo.SyncRequest; @@ -24,8 +27,29 @@ public FindByExampleHandler() { public SyncResponse handleMessage(SyncRequest request, String replyTo) throws Exception { FindByExampleResponse response = new FindByExampleResponse(); FindByExampleRequest findByExampleRequest = (FindByExampleRequest) request; - Object result = syncAgentService.findByExample(findByExampleRequest.getTheObject(), null, findByExampleRequest.getClientId()); + String originalId = null; + if (StringUtils.hasText(findByExampleRequest.getTheObject().getID())) { + originalId = findByExampleRequest.getTheObject().getID(); + findByExampleRequest.getTheObject().setID(null); + } + List result = (List) syncAgentService.findByExample(findByExampleRequest.getTheObject(), null, findByExampleRequest.getClientId()); response = new FindByExampleResponse(); + + if (result == null || result.isEmpty()) { + if (originalId != null) { + // Couldn't find by example, but if the original object had an ID, let's try and find by id. + BaseDataObject findByIdResult = (BaseDataObject) syncAgentService.findById( + new ClassIDPair(originalId, findByExampleRequest.getTheObject().getClass().getCanonicalName()), + findByExampleRequest.getClientId()); + if (findByIdResult != null) { + if (result == null) { + result = new ArrayList(); + } + result.add(findByIdResult); + } + } + } + ((FindByExampleResponse)response).setResult((List)result); return response; diff --git a/src/main/java/com/percero/amqp/handlers/ReconnectHandler.java b/src/main/java/com/percero/amqp/handlers/ReconnectHandler.java index ac3a4d2..f6083a6 100644 --- a/src/main/java/com/percero/amqp/handlers/ReconnectHandler.java +++ b/src/main/java/com/percero/amqp/handlers/ReconnectHandler.java @@ -91,14 +91,16 @@ public SyncResponse handleMessage(SyncRequest request, String replyTo) throws Ex reconnectRequest.getExistingClientIds()[0] = reconnectRequest.getExistingClientId(); } + String matchingClientId = null; for(int i=0; i itrExistingClientIds = existingClientIds.iterator(); - while (itrExistingClientIds.hasNext()) { - String existingClientId = itrExistingClientIds.next(); - if (existingClientId.equalsIgnoreCase(replyTo)) { - // We have already retrieved the Journals for this client IDs. - continue; + if (existingClientIds != null && !existingClientIds.isEmpty()) { + Iterator itrExistingClientIds = existingClientIds.iterator(); + while (itrExistingClientIds.hasNext()) { + String existingClientId = itrExistingClientIds.next(); + if (existingClientId.equalsIgnoreCase(replyTo)) { + // We have already retrieved the Journals for this client IDs. + continue; + } + + // ExistingClientId is different than new clientId, need to transfer all messages from old queue and then remove that queue. + log.debug("Renaming client " + existingClientId + " to " + replyTo); + pushSyncHelper.renameClient(existingClientId, replyTo); + + // TODO: Do we need to also get updates/deletes for existingClientId? + + // Push all UpdateJournals for this Client. + updateJournals.addAll( accessManager.getClientUpdateJournals(existingClientId, true) ); + log.debug((updateJournals != null ? updateJournals.size() : 0) + " updateJournal(s) for Client"); + + // Push all DeleteJournals for this Client. + deleteJournals.addAll( accessManager.getClientDeleteJournals(existingClientId, true) ); + log.debug((deleteJournals != null ? deleteJournals.size() : 0) + " deleteJournal(s) for Client"); } - - // ExistingClientId is different than new clientId, need to transfer all messages from old queue and then remove that queue. - log.debug("Renaming client " + existingClientId + " to " + replyTo); - pushSyncHelper.renameClient(existingClientId, replyTo); - - // TODO: Do we need to also get updates/deletes for existingClientId? - - // Push all UpdateJournals for this Client. - updateJournals.addAll( accessManager.getClientUpdateJournals(existingClientId, true) ); - log.debug((updateJournals != null ? updateJournals.size() : 0) + " updateJournal(s) for Client"); - - // Push all DeleteJournals for this Client. - deleteJournals.addAll( accessManager.getClientDeleteJournals(existingClientId, true) ); - log.debug((deleteJournals != null ? deleteJournals.size() : 0) + " deleteJournal(s) for Client"); } // syncAgentService.pushClientUpdateJournals(reconnectRequest.getClientId(), updateJournals); diff --git a/src/main/java/com/percero/amqp/handlers/SyncMessageHandler.java b/src/main/java/com/percero/amqp/handlers/SyncMessageHandler.java index 3910d7a..c38f994 100644 --- a/src/main/java/com/percero/amqp/handlers/SyncMessageHandler.java +++ b/src/main/java/com/percero/amqp/handlers/SyncMessageHandler.java @@ -43,7 +43,12 @@ public void run(SyncRequest request, String replyTo) { response = handleMessage(request, replyTo); } catch(SyncException e) { if (ClientException.INVALID_CLIENT.equals(e.getName())) { - log.debug("Invalid Client - Setting response to NULL", e); + if (e instanceof ClientException) { + log.debug("Invalid Client (" + ((ClientException) e).getClientId() + ") - Setting response to NULL", e); + } + else { + log.debug("Invalid Client - Setting response to NULL", e); + } // No need to send a response to an invalid client. response = null; diff --git a/src/main/java/com/percero/datasource/BaseConnectionFactory.java b/src/main/java/com/percero/datasource/BaseConnectionFactory.java index 440725e..e16664b 100644 --- a/src/main/java/com/percero/datasource/BaseConnectionFactory.java +++ b/src/main/java/com/percero/datasource/BaseConnectionFactory.java @@ -169,6 +169,7 @@ public void init() throws PropertyVetoException{ // Default to Hikari Connection Pool. HikariConfig config = new HikariConfig(); config.setDriverClassName(driverClassName); + config.setRegisterMbeans(true); config.setJdbcUrl(jdbcUrl); config.setUsername(username); config.setPassword(password); diff --git a/src/main/java/com/percero/datasource/sql/BaseSqlDataConnectionRegistry.java b/src/main/java/com/percero/datasource/sql/BaseSqlDataConnectionRegistry.java new file mode 100644 index 0000000..b4a2aac --- /dev/null +++ b/src/main/java/com/percero/datasource/sql/BaseSqlDataConnectionRegistry.java @@ -0,0 +1,102 @@ +package com.percero.datasource.sql; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; + +import javax.annotation.PostConstruct; + +import org.apache.log4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import com.esotericsoftware.yamlbeans.YamlException; +import com.esotericsoftware.yamlbeans.YamlReader; +import com.percero.agents.sync.dao.DAORegistry; +import com.percero.agents.sync.jobs.UpdateTableRegistry; +import com.percero.agents.sync.services.DAODataProvider; +import com.percero.agents.sync.services.DataProviderManager; +import com.percero.datasource.IConnectionFactory; + +@Component +public class BaseSqlDataConnectionRegistry { + + @Autowired + DataProviderManager dataProviderManager; + + @Autowired + DAODataProvider daoDataProvider; + + @Autowired(required=false) @Qualifier("daoSqlConnectionFactoryConfigFile") + String configFile = "sql/connectionFactories.yml"; + + private static Logger logger = Logger.getLogger(DAORegistry.class); + + private static BaseSqlDataConnectionRegistry instance; + + public static BaseSqlDataConnectionRegistry getInstance() { + if (instance == null) { + instance = new BaseSqlDataConnectionRegistry(); + } + return instance; + } + + public BaseSqlDataConnectionRegistry() { + logger.info("PsiGlobalDataConnectionRegistry"); + instance = this; + } + + private static Map connectionFactories; + + @PostConstruct + public void init() throws YamlException { + if (!StringUtils.hasText(configFile)) { + configFile = "sql/connectionFactories.yml"; + } + + logger.info("Using Connection Factories configuration file " + configFile); + dataProviderManager.addDataProvider(daoDataProvider); + dataProviderManager.setDefaultDataProvider(daoDataProvider); + + // Init the connection factories here. + connectionFactories = new HashMap(); + + try { + URL ymlUrl = UpdateTableRegistry.class.getClassLoader().getResource(configFile); + if (ymlUrl == null) { + logger.warn("No configuration found for Connection Factories (" + configFile + "), skipping Connection Factories"); + return; + } + File configFile = new File(ymlUrl.getFile()); + YamlReader reader = new YamlReader(new FileReader(configFile)); + + while (true) { + IConnectionFactory connectionFactory = reader.read(IConnectionFactory.class); + if (connectionFactory == null) { + break; + } + + logger.info("ConnectionFactory configured: " + connectionFactory.getName()); + + connectionFactories.put(connectionFactory.getName(), connectionFactory); + } + } + catch (FileNotFoundException e) { + logger.warn("No configuration found for StoredProcedures (storedProcedures.yml), skipping StoredProcedures"); + } + } + + public void registerConnectionFactory(String name, IConnectionFactory cf) { + connectionFactories.put(name, cf); + } + + public IConnectionFactory getConnectionFactory(String name) { + return connectionFactories.get(name); + } + +} diff --git a/src/main/java/com/percero/datasource/sql/SqlConnectionFactory.java b/src/main/java/com/percero/datasource/sql/SqlConnectionFactory.java new file mode 100644 index 0000000..0dd4d43 --- /dev/null +++ b/src/main/java/com/percero/datasource/sql/SqlConnectionFactory.java @@ -0,0 +1,315 @@ +package com.percero.datasource.sql; + +import java.sql.Connection; +import java.sql.SQLException; + +import javax.sql.DataSource; + +import org.apache.log4j.Logger; +import org.springframework.util.StringUtils; + +import com.mchange.v2.c3p0.ComboPooledDataSource; +import com.percero.agents.sync.services.DAODataProvider; +import com.percero.agents.sync.services.DataProviderManager; +import com.percero.datasource.IConnectionFactory; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; + +/** + * See http://www.mchange.com/projects/c3p0/ for configuration tuning. + * + */ +public class SqlConnectionFactory implements IConnectionFactory { + + private static Logger logger = Logger.getLogger(SqlConnectionFactory.class); + + private static final long MAX_CONNECT_TIME = 2500; // 2.5 Seconds. + + private static final String HIKARI_CONNECTION_POOL = "hikari"; + private static final String C3P0_CONNECTION_POOL = "c3p0"; + + public SqlConnectionFactory() { + + } + + private String name; + private String preferredConnectionPool = HIKARI_CONNECTION_POOL; + private String driverClassName; + private String username; + private String password; + private String jdbcUrl; + private Integer acquireIncrement = 4; + private Integer minPoolSize = 4; + private Integer maxPoolSize = 52; + private Integer maxIdleTime = 60 * 30; // 30 Minutes + private String testQuery = null; + private Integer fetchSize = 100; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPreferredConnectionPool() { + return preferredConnectionPool; + } + + public void setPreferredConnectionPool(String preferredConnectionPool) { + this.preferredConnectionPool = preferredConnectionPool; + } + + public String getDriverClassName() { + return driverClassName; + } + + public void setDriverClassName(String driverClassName) { + this.driverClassName = driverClassName; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getJdbcUrl() { + return jdbcUrl; + } + + public void setJdbcUrl(String jdbcUrl) { + this.jdbcUrl = jdbcUrl; + } + + public Integer getAcquireIncrement() { + return acquireIncrement; + } + + public void setAcquireIncrement(Integer acquireIncrement) { + this.acquireIncrement = acquireIncrement; + } + + public Integer getMinPoolSize() { + return minPoolSize; + } + + public void setMinPoolSize(Integer minPoolSize) { + this.minPoolSize = minPoolSize; + } + + public Integer getMaxPoolSize() { + return maxPoolSize; + } + + public void setMaxPoolSize(Integer maxPoolSize) { + this.maxPoolSize = maxPoolSize; + } + + public Integer getMaxIdleTime() { + return maxIdleTime; + } + + public void setMaxIdleTime(Integer maxIdleTime) { + this.maxIdleTime = maxIdleTime; + } + + public String getTestQuery() { + return testQuery; + } + + public void setTestQuery(String testQuery) { + this.testQuery = testQuery; + } + + public Integer getFetchSize() { + // Default to 100; + if (fetchSize == null || fetchSize <= 0) { + return 100; + } + return fetchSize; + } + + public void setFetchSize(Integer fetchSize) { + this.fetchSize = fetchSize; + } + + // private ComboPooledDataSource cpds; + private DataSource ds; + + private volatile boolean initialized = false; + + public synchronized void init() throws Exception { + if (initialized) { + return; + } + try { + logger.info("Initializing connection factory: " + getName()); + + if (C3P0_CONNECTION_POOL.equalsIgnoreCase(preferredConnectionPool)) { + ComboPooledDataSource cpds = new ComboPooledDataSource(); + cpds.setDriverClass(driverClassName); // loads the jdbc driver + cpds.setJdbcUrl(jdbcUrl); + cpds.setUser(username); + cpds.setPassword(password); + + // the settings below are optional -- c3p0 can work with + // defaults + if (minPoolSize != null) { + cpds.setMinPoolSize(minPoolSize); + } + if (acquireIncrement != null) { + cpds.setAcquireIncrement(acquireIncrement); + } + if (maxPoolSize != null) { + cpds.setMaxPoolSize(maxPoolSize); + } + if (maxIdleTime != null) { + cpds.setMaxIdleTime(maxIdleTime); + cpds.setIdleConnectionTestPeriod(maxIdleTime); + } + cpds.setNumHelperThreads(30); + cpds.setTestConnectionOnCheckout(true); + if (!StringUtils.hasText(testQuery)) { + defaultTestQuery(); + } + cpds.setPreferredTestQuery(testQuery); + + ds = cpds; + } else { + // Default to Hikari Connection Pool. + HikariConfig config = new HikariConfig(); + // config.setDriverClassName(driverClassName); + config.setJdbcUrl(jdbcUrl); + config.setUsername(username); + config.setPassword(password); + config.addDataSourceProperty("cachePrepStmts", "true"); + config.addDataSourceProperty("prepStmtCacheSize", "250"); + config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048"); + + config.setPoolName(name); + if (minPoolSize != null) { + config.setMinimumIdle(minPoolSize); + } + // if (acquireIncrement != null) { + // config.setAcquireIncrement(acquireIncrement); + // } + if (maxPoolSize != null) { + config.setMaximumPoolSize(maxPoolSize); + } + if (maxIdleTime != null) { + config.setIdleTimeout(maxIdleTime * 1000); // Convert to + // milliseconds + } + // config.setNumHelperThreads(30); + // config.setTestConnectionOnCheckout(true); + if (!StringUtils.hasText(testQuery)) { + defaultTestQuery(); + } + config.setConnectionTestQuery(testQuery); + + ds = new HikariDataSource(config); + } + + BaseSqlDataConnectionRegistry.getInstance() + .registerConnectionFactory(getName(), this); + DataProviderManager.getInstance().setDefaultDataProvider( + DAODataProvider.getInstance()); + + initialized = true; + } catch (Exception pve) { + logger.error(pve.getMessage(), pve); + throw pve; + } + } + +/** + * + */ +private void defaultTestQuery() { + if (driverClassName.toLowerCase().contains("mysql") || driverClassName.toLowerCase().contains("h2") || driverClassName.toLowerCase().contains("mssql") || driverClassName.toLowerCase().contains("postgre") || driverClassName.toLowerCase().contains("sqlite")) { + testQuery = "SELECT 1"; + } + else if (driverClassName.toLowerCase().contains("oracle")) { + testQuery = "SELECT 1 from dual"; + } + else if (driverClassName.toLowerCase().contains("hsql")) { + testQuery = "SELECT 1 FROM INFORMATION_SCHEMA.SYSTEM_USERS"; + } + else if (driverClassName.toLowerCase().contains("db2")) { + testQuery = "SELECT 1 FROM SYSIBM.SYSDUMMY1"; + } + else if (driverClassName.toLowerCase().contains("infomix")) { + testQuery = "select count(*) from systables"; + } + else { + testQuery = "SELECT 1"; + } +} + + /* + * (non-Javadoc) + * + * @see com.pulse.dataprovider.IConnectionFactory#getConnection() + */ + @Override + public Connection getConnection() throws SQLException { + long timeStart = System.currentTimeMillis(); + try { + if (!initialized) { + try { + init(); + } catch (Exception e) { + logger.error("Error initializing SqlConnectionFactory: " + + this.getName(), e); + } + } + Connection result = ds.getConnection(); + // Connection result = cpds.getConnection(); + logger.debug("Database Connection Time: " + + (System.currentTimeMillis() - timeStart) + "ms [" + + this.getName() + ": " + this.getJdbcUrl() + "]"); + return result; + } catch (SQLException e) { + logger.error(e.getMessage(), e); + throw e; + } finally { + long timeEnd = System.currentTimeMillis(); + long totalTime = timeEnd - timeStart; + if (totalTime > MAX_CONNECT_TIME) { + logger.warn("Long Database Connection: " + totalTime + "ms [" + + this.getName() + ": " + this.getJdbcUrl() + "]"); + } + } + } + + // public static void main(String[] args) throws Exception{ + // UpdateTableConnectionFactory cf = new UpdateTableConnectionFactory(); + // cf.driverClassName = "com.mysql.jdbc.Driver"; + // cf.jdbcUrl = "jdbc:mysql://localhost/test"; + // cf.username = "root"; + // cf.password = "root"; + // cf.init(); + // + // Connection c = cf.getConnection(); + // Statement stmt = c.createStatement(); + // + // + // ResultSet rs = stmt.executeQuery("SELECT * FROM Account"); + // while(rs.next()) { + // logger.info("ID: " + rs.getInt("ID")); + // logger.info("markedForRemoval: "+ rs.getDate("markedForRemoval")); + // } + // } +} diff --git a/src/main/java/com/percero/datasource/sql/SqlDataAccessObject.java b/src/main/java/com/percero/datasource/sql/SqlDataAccessObject.java new file mode 100644 index 0000000..33f7d69 --- /dev/null +++ b/src/main/java/com/percero/datasource/sql/SqlDataAccessObject.java @@ -0,0 +1,927 @@ +package com.percero.datasource.sql; + +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; + +import org.apache.log4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.StringUtils; + +import com.percero.agents.sync.connectors.ConnectorException; +import com.percero.agents.sync.dao.IDataAccessObject; +import com.percero.agents.sync.exceptions.SyncDataException; +import com.percero.agents.sync.exceptions.SyncException; +import com.percero.agents.sync.metadata.IMappedClassManager; +import com.percero.agents.sync.metadata.MappedClassManagerFactory; +import com.percero.agents.sync.metadata.MappedField; +import com.percero.agents.sync.services.IDataProviderManager; +import com.percero.agents.sync.services.ISyncAgentService; +import com.percero.agents.sync.vo.BaseDataObject; +import com.percero.agents.sync.vo.ClassIDPair; +import com.percero.agents.sync.vo.ClassIDPairs; +import com.percero.datasource.IConnectionFactory; +import com.percero.framework.vo.IPerceroObject; +import com.percero.framework.vo.PerceroList; + +public abstract class SqlDataAccessObject implements IDataAccessObject { + + static final Logger log = Logger.getLogger(SqlDataAccessObject.class); + + public static long LONG_RUNNING_QUERY_TIME = 2000; + public static int QUERY_TIMEOUT = 10; + + public SqlDataAccessObject() { + super(); + + // We want to use the UTC time zone + TimeZone timeZone = TimeZone.getTimeZone("UTC"); + TimeZone.setDefault(timeZone); + } + + @Autowired + ISyncAgentService syncAgentService; + + @Autowired + protected IDataProviderManager dataProviderManager; + + protected IMappedClassManager mcm = MappedClassManagerFactory.getMappedClassManager(); + + BaseSqlDataConnectionRegistry connectionRegistry; + public BaseSqlDataConnectionRegistry getConnectionRegistry() { + if (connectionRegistry == null) { + connectionRegistry = BaseSqlDataConnectionRegistry.getInstance(); + } + return connectionRegistry; + } + + protected String getConnectionFactoryName() { + return null; + } + +// protected String getSqlView() { +// return ""; +// } +// +// protected String getSelectFromStatementTableName(boolean shellOnly) { +// return ""; +// } +// +// protected String getInnerJoinStatement() { +// return ""; +// } +// +// protected String getWhereIdEqualsClause(String equalTo, boolean isFirst) { +// return ""; +// } +// +// protected String getWhereClause(boolean shellOnly) { +// return ""; +// } +// +// protected String getWhereInClause(boolean shellOnly) { +// return ""; +// } +// +// protected String getIdColumnName() { +// return "ID"; +// } +// + protected String getSelectStarSQL() { + return null; + } + + protected String getSelectShellOnlySQL() { + return null; + } + + protected String getSelect(Boolean shellOnly) { + if (shellOnly) { + return getSelectShellOnlySQL(); + } + else { + return getSelectStarSQL(); + } + } + + protected String getSelectInStarSQL() { + return null; + } + + protected String getSelectInShellOnlySQL() { + return null; + } + + protected String getSelectIn(Boolean shellOnly) { + if (shellOnly) { + return getSelectInShellOnlySQL(); + } + else { + return getSelectInStarSQL(); + } + } + + protected String getSelectByRelationshipStarSQL(String joinColumnName) throws SyncDataException { + return null; + } + + protected String getSelectByRelationshipShellOnlySQL(String joinColumnName) { + return null; + } + + protected String getSelectByRelationship(String joinColumnName, Boolean shellOnly) throws SyncDataException { + if (shellOnly) { + return getSelectByRelationshipShellOnlySQL(joinColumnName); + } + else { + return getSelectByRelationshipStarSQL(joinColumnName); + } + } + + protected String getSelectAllSql(Boolean shellOnly) throws SyncException { + if (shellOnly) { + return getSelectAllShellOnlySQL(); + } + else { + return getSelectAllStarSQL(); + } + } + + protected String getFindByExampleSelectSql(Boolean shellOnly) { + if (shellOnly) { + return getFindByExampleSelectShellOnlySQL(); + } + else { + return getFindByExampleSelectAllStarSQL(); + } + } + + protected String getFindByExampleSelectShellOnlySQL() { + return null; + } + + protected String getFindByExampleSelectAllStarSQL() { + return null; + } + + protected String getSelectAllStarSQL() throws SyncException { + return null; + } + + protected String getSelectAllStarWithLimitAndOffsetSQL() { + return null; + } + + protected String getSelectAllShellOnlySQL() throws SyncException { + return null; + } + + protected String getSelectAllShellOnlyWithLimitAndOffsetSQL() { + return null; + } + + protected String getCountAllSQL() { + return null; + } + + public T retrieveObject(ClassIDPair classIdPair, String userId, + Boolean shellOnly) throws SyncException { + T result = null; + + if (!StringUtils.hasText(classIdPair.getID())) { + // Empty or NULL ID. + return result; + } + + String sql = getSelect(shellOnly); + List results = executeSelectById(sql, classIdPair.getID(), shellOnly); + if (results != null && !results.isEmpty()) { + result = results.get(0); + + if (result instanceof BaseDataObject){ + ((BaseDataObject)result).setDataSource(BaseDataObject.DATA_SOURCE_DATA_STORE); + } + } + + return result; + } + + public List retrieveObjects(ClassIDPairs classIdPairs, + String userId, Boolean shellOnly) throws SyncException { + // We are just selecting all the columns for this object, which map to Properties and Source Relationship ID's. + // The order of these in the SELECT doesn't matter, it just needs to match the same order that we are retrieving them + // below when we fill in the actual object. + List results = new ArrayList(); + + String questionMarkString = ""; + for(int i=0; i 0) { + questionMarkString += ","; + } + questionMarkString += "?"; + } + + String sql = getSelectIn(shellOnly); + sql = sql.replace("?", questionMarkString); + log.debug("running retrieveObjects query: \n"+sql); + + long timeStart = System.currentTimeMillis(); + + Connection conn = null; + PreparedStatement pstmt = null; + try { + IConnectionFactory connectionFactory = getConnectionRegistry().getConnectionFactory(getConnectionFactoryName()); + conn = connectionFactory.getConnection(); + pstmt = conn.prepareStatement(sql); + pstmt.setFetchSize(connectionFactory.getFetchSize()); + pstmt.setQueryTimeout(QUERY_TIMEOUT); + + int counter = 1; // PreparedStatement index starts at 1. + Iterator itrIds = classIdPairs.getIds().iterator(); + while (itrIds.hasNext()) { + String nextId = itrIds.next(); + pstmt.setString(counter, nextId); + counter++; + } + + ResultSet rs = pstmt.executeQuery(); + while (rs.next()) { + T nextResult = extractObjectFromResultSet(rs, shellOnly, null); + if (nextResult instanceof BaseDataObject){ + ((BaseDataObject)nextResult).setDataSource(BaseDataObject.DATA_SOURCE_DATA_STORE); + } + results.add(nextResult); + } + } catch(Exception e) { + log.error("Unable to retrieveObjects\n" + sql, e); + throw new SyncDataException(e); + } finally { + try { + if (pstmt != null) { + pstmt.close(); + } + if (conn != null) { + conn.close(); + } + } catch (Exception e) { + log.error("Error closing database statement/connection", e); + } + } + + long timeEnd = System.currentTimeMillis(); + long totalTime = timeEnd - timeStart; + if (totalTime > LONG_RUNNING_QUERY_TIME) { + String idsStr = ""; + Iterator itrIds = classIdPairs.getIds().iterator(); + int counter = 0; + while (itrIds.hasNext()) { + String nextId = itrIds.next(); + if (counter > 0) { + idsStr += ","; + } + idsStr += nextId; + counter++; + } + log.warn("LONG RUNNING QUERY: " + totalTime + "ms\n" + sql + "\n Ids: " + idsStr); + } + + return results; + } + + public List retrieveAllByRelationship(MappedField mappedField, ClassIDPair targetClassIdPair, Boolean shellOnly, String userId) throws SyncException { + + String sql = getSelectByRelationship(mappedField.getJoinColumnName(), shellOnly); + + List results = executeSelectById(sql, targetClassIdPair.getID(), shellOnly); + return results; + } + + public List findByExample(T theQueryObject, + List excludeProperties, String userId, Boolean shellOnly) throws SyncException { + return null; + } + + /** + * @param selectQueryString + * @return + * @throws SyncDataException + */ + protected List executeSelect(String selectQueryString, Boolean shellOnly) + throws SyncDataException { + List results = new ArrayList(); + log.debug("running executeSelect query: \n"+selectQueryString); + + long timeStart = System.currentTimeMillis(); + + // Open the database session. + Connection conn = null; + Statement stmt = null; + try { + IConnectionFactory connectionFactory = getConnectionRegistry().getConnectionFactory(getConnectionFactoryName()); + conn = connectionFactory.getConnection(); + stmt = conn.createStatement(); + stmt.setFetchSize(connectionFactory.getFetchSize()); + stmt.setQueryTimeout(QUERY_TIMEOUT); + + ResultSet rs = stmt.executeQuery(selectQueryString); + while (rs.next()) { + T nextResult = extractObjectFromResultSet(rs, shellOnly, null); + if (nextResult instanceof BaseDataObject){ + ((BaseDataObject)nextResult).setDataSource(BaseDataObject.DATA_SOURCE_DATA_STORE); + } + results.add(nextResult); + } + } catch(Exception e) { + log.error("Unable to retrieveObjects\n" + selectQueryString, e); + throw new SyncDataException(e); + } finally { + try { + if (stmt != null) { + stmt.close(); + } + if (conn != null) { + conn.close(); + } + } catch (Exception e) { + log.error("Error closing database statement/connection", e); + } + } + + long timeEnd = System.currentTimeMillis(); + long totalTime = timeEnd - timeStart; + if (totalTime > LONG_RUNNING_QUERY_TIME) { + log.warn("LONG RUNNING QUERY: " + totalTime + "ms\n" + selectQueryString); + } + + return results; + } + + protected List executeSelectById(String selectQueryString, String id, Boolean shellOnly) + throws SyncDataException { + List results = new ArrayList(); + + if (!StringUtils.hasText(id) || "null".equalsIgnoreCase(id)) { + // Make sure we have a valid ID. + log.debug("SelectById: Skipping NULL or empty Id"); + return results; + } + + log.debug("running selectById query: \n"+selectQueryString+"\nID: "+id); + + long timeStart = System.currentTimeMillis(); + + // Open the database session. + Connection conn = null; + PreparedStatement pstmt = null; + try { + IConnectionFactory connectionFactory = getConnectionRegistry().getConnectionFactory(getConnectionFactoryName()); + conn = connectionFactory.getConnection(); + pstmt = conn.prepareStatement(selectQueryString); + pstmt.setFetchSize(connectionFactory.getFetchSize()); + pstmt.setQueryTimeout(QUERY_TIMEOUT); + pstmt.setString(1, id); + + ResultSet rs = pstmt.executeQuery(); + while (rs.next()) { + T nextResult = extractObjectFromResultSet(rs, shellOnly, null); + if (nextResult instanceof BaseDataObject){ + ((BaseDataObject)nextResult).setDataSource(BaseDataObject.DATA_SOURCE_DATA_STORE); + } + results.add(nextResult); + } + } catch(Exception e) { + log.error("Unable to retrieveObject:\n" + selectQueryString + "\n ID: " + id, e); + throw new SyncDataException(e); + } finally { + try { + if (pstmt != null) { + pstmt.close(); + } + if (conn != null) { + conn.close(); + } + } catch (Exception e) { + log.error("Error closing database statement/connection", e); + } + } + + long timeEnd = System.currentTimeMillis(); + long totalTime = timeEnd - timeStart; + if (totalTime > LONG_RUNNING_QUERY_TIME) { + log.warn("LONG RUNNING QUERY: " + totalTime + "ms\n" + selectQueryString+"\n ID: "+id); + } + + return results; + } + + protected List executeSelectWithParams(String selectQueryString, Object[] paramValues, Boolean shellOnly) + throws SyncDataException { + List results = new ArrayList(); + log.debug("running executeSelectWithParams query: \n"+selectQueryString+"\nparams: "+paramValues); + + long timeStart = System.currentTimeMillis(); + + // Open the database session. + Connection conn = null; + PreparedStatement pstmt = null; + try { + IConnectionFactory connectionFactory = getConnectionRegistry().getConnectionFactory(getConnectionFactoryName()); + conn = connectionFactory.getConnection(); + pstmt = conn.prepareStatement(selectQueryString); + pstmt.setFetchSize(connectionFactory.getFetchSize()); + pstmt.setQueryTimeout(QUERY_TIMEOUT); + + for(int i=0; i LONG_RUNNING_QUERY_TIME) { + String paramsString = ""; + for(Object nextParamValue : paramValues) { + if (nextParamValue instanceof String) { + paramsString += "\n String: `" + (String) nextParamValue + "`"; + } + else if (nextParamValue instanceof Integer) { + paramsString += "\n Integer: " + ((Integer) nextParamValue).toString(); + } + else if (nextParamValue instanceof Double) { + paramsString += "\n Double: " + ((Double) nextParamValue).toString(); + } + else if (nextParamValue instanceof Date) { + paramsString += "\n Date: " + ((Date) nextParamValue).toString(); + } + else { + paramsString += "\n UNKNOWN: " + nextParamValue.toString(); + } + } + log.warn("LONG RUNNING QUERY: " + totalTime + "ms\n" + selectQueryString + paramsString); + } + + return results; + } + + protected T extractObjectFromResultSet(ResultSet rs, Boolean shellOnly, T perceroObject) throws SQLException { + throw new SQLException("Method must be overridden"); + } + + public PerceroList getAll(Integer pageNumber, Integer pageSize, Boolean returnTotal, String userId, Boolean shellOnly) throws Exception { + + boolean useLimit = pageNumber != null && pageSize != null && pageSize > 0; + String sql = null; + if (useLimit) { + if (shellOnly) { + sql = getSelectAllShellOnlyWithLimitAndOffsetSQL(); + } + else { + sql = getSelectAllStarWithLimitAndOffsetSQL(); + } + } + else { + sql = getSelectAllSql(shellOnly); + } + List objects = new ArrayList(); + + long timeStart = System.currentTimeMillis(); + + // Open the database session. + Connection conn = null; + PreparedStatement pstmt = null; + try { + log.debug("running getAll query: \n"+sql); + + IConnectionFactory connectionFactory = getConnectionRegistry().getConnectionFactory(getConnectionFactoryName()); + conn = connectionFactory.getConnection(); + pstmt = conn.prepareStatement(sql); + pstmt.setFetchSize(connectionFactory.getFetchSize()); + pstmt.setQueryTimeout(QUERY_TIMEOUT); + + if (useLimit) { + pstmt.setInt(1, pageSize); + pstmt.setInt(2, pageNumber * pageSize); + } + + ResultSet rs = pstmt.executeQuery(); + while (rs.next()) { + T nextResult = extractObjectFromResultSet(rs, shellOnly, null); + if (nextResult instanceof BaseDataObject){ + ((BaseDataObject)nextResult).setDataSource(BaseDataObject.DATA_SOURCE_DATA_STORE); + } + objects.add(nextResult); + } + } catch(Exception e) { + log.error("Unable to retrieveObjects\n" + sql, e); + throw new SyncDataException(e); + } finally { + try { + if (pstmt != null) { + pstmt.close(); + } + if (conn != null) { + conn.close(); + } + } catch (Exception e) { + log.error("Error closing database statement/connection", e); + } + } + + long timeEnd = System.currentTimeMillis(); + long totalTime = timeEnd - timeStart; + if (totalTime > LONG_RUNNING_QUERY_TIME) { + log.warn("LONG RUNNING QUERY: " + totalTime + "ms\n" + sql + + (useLimit ? "\n LIMIT: " + pageSize.toString() + "\n PAGE: " + pageNumber.toString() : "")); + } + + PerceroList results = new PerceroList(objects); + results.setPageNumber(pageNumber); + results.setPageSize(pageSize); + + if (returnTotal != null && returnTotal) { + Integer total = countAll(userId); + results.setTotalLength(total); + } + + return results; + } + + public Integer countAll(String userId) throws SyncException { + + String sql = getCountAllSQL(); + log.debug("running countAll query: \n"+sql); + + long timeStart = System.currentTimeMillis(); + + // Open the database session. + Connection conn = null; + Statement stmt = null; + Integer result = null; + try { + IConnectionFactory connectionFactory = getConnectionRegistry().getConnectionFactory(getConnectionFactoryName()); + conn = connectionFactory.getConnection(); + stmt = conn.createStatement(); + stmt.setQueryTimeout(QUERY_TIMEOUT); + + ResultSet rs = stmt.executeQuery(sql); + if (rs.next()) { + result = rs.getInt(1); + + } + } catch(Exception e) { + log.error("Unable to executeSelectCount\n" + sql, e); + throw new SyncDataException(e); + } finally { + try { + if (stmt != null) { + stmt.close(); + } + if (conn != null) { + conn.close(); + } + } catch (Exception e) { + log.error("Error closing database statement/connection", e); + } + } + + long timeEnd = System.currentTimeMillis(); + long totalTime = timeEnd - timeStart; + if (totalTime > LONG_RUNNING_QUERY_TIME) { + log.warn("LONG RUNNING QUERY: " + totalTime + "ms\n" + sql); + } + + return result; + } + + protected int executeUpdate(String sqlStatement) throws SyncDataException { + + long timeStart = System.currentTimeMillis(); + + Connection conn = null; + Statement stmt = null; + int result = 0; + try { + IConnectionFactory connectionFactory = getConnectionRegistry().getConnectionFactory(getConnectionFactoryName()); + conn = connectionFactory.getConnection(); + stmt = conn.createStatement(); + stmt.setQueryTimeout(QUERY_TIMEOUT); + + result = stmt.executeUpdate(sqlStatement); + + } catch(Exception e) { + log.error("Unable to executeUpdate\n" + sqlStatement, e); + throw new SyncDataException(e); + } finally { + try { + if (stmt != null) { + stmt.close(); + } + if (conn != null) { + conn.close(); + } + } catch (Exception e) { + log.error("Error closing database statement/connection", e); + } + } + + long timeEnd = System.currentTimeMillis(); + long totalTime = timeEnd - timeStart; + if (totalTime > LONG_RUNNING_QUERY_TIME) { + log.warn("LONG RUNNING QUERY: " + totalTime + "ms\n" + sqlStatement); + } + + return result; + } + + @Override + public Boolean hasCreateAccess(ClassIDPair classIdPair, String userId) { + // Defaults to true + return true; + } + + @Override + public Boolean hasReadAccess(ClassIDPair classIdPair, String userId) { + // Defaults to true + return true; + } + + @Override + public Boolean hasUpdateAccess(ClassIDPair classIdPair, String userId) { + // Defaults to true + return true; + } + + @Override + public Boolean hasDeleteAccess(ClassIDPair classIdPair, String userId) { + // Defaults to true + return true; + } + + public List runQuery(String selectQueryString, Object[] paramValues, String userId) throws SyncException { + if (!StringUtils.hasText(selectQueryString)) { + return new ArrayList(0); + } + + List results = new ArrayList(); + log.debug("running executeSelectWithParams query: \n"+selectQueryString+"\nparams: "+paramValues); + + long timeStart = System.currentTimeMillis(); + + // Open the database session. + Connection conn = null; + PreparedStatement pstmt = null; + try { + IConnectionFactory connectionFactory = getConnectionRegistry().getConnectionFactory(getConnectionFactoryName()); + conn = connectionFactory.getConnection(); + pstmt = conn.prepareStatement(selectQueryString); + pstmt.setFetchSize(connectionFactory.getFetchSize()); + pstmt.setQueryTimeout(QUERY_TIMEOUT); + + if (paramValues != null) { + for(int i=0; i LONG_RUNNING_QUERY_TIME) { + String paramsString = ""; + if (paramValues != null) { + for(Object nextParamValue : paramValues) { + if (nextParamValue instanceof String) { + paramsString += "\n String: `" + (String) nextParamValue + "`"; + } + else if (nextParamValue instanceof Integer) { + paramsString += "\n Integer: " + ((Integer) nextParamValue).toString(); + } + else if (nextParamValue instanceof Double) { + paramsString += "\n Double: " + ((Double) nextParamValue).toString(); + } + else if (nextParamValue instanceof Date) { + paramsString += "\n Date: " + ((Date) nextParamValue).toString(); + } + else { + paramsString += "\n UNKNOWN: " + nextParamValue.toString(); + } + } + } + log.warn("LONG RUNNING QUERY: " + totalTime + "ms\n" + selectQueryString + paramsString); + } + + return results; + } + + // TODO: Fill this out + public T cleanObjectForUser(T perceroObject, + String userId) { + return perceroObject; + } + + protected String getUpdateCallableStatementSql() { + return ""; + } + protected String getUpdateCallableStatementParams() { + return "?"; + } + protected String getInsertCallableStatementSql() { + return ""; + } + protected String getInsertCallableStatementParams() { + return "?"; + } + protected String getDeleteCallableStatementSql() { + return ""; + } + + protected void setCallableStatementInsertParams(T perceroObject, CallableStatement pstmt) throws SQLException { + + } + + protected void setCallableStatementUpdateParams(T perceroObject, CallableStatement pstmt) throws SQLException { + + } + + protected int runStoredProcedure(T perceroObject, String userId, String storedProcedureCallSql) throws ConnectorException { + int result = 0; + IConnectionFactory connectionFactory = getConnectionRegistry().getConnectionFactory(getConnectionFactoryName()); + try (Connection conn = connectionFactory.getConnection(); Statement statement = conn.createStatement()) { + CallableStatement cstmt = conn.prepareCall(storedProcedureCallSql); + setBaseStatementInsertParams(perceroObject, userId, cstmt); + result = cstmt.executeUpdate(); + } catch (SQLException e) { + try (Connection conn = connectionFactory.getConnection(); Statement statement = conn.createStatement()) { + CallableStatement cstmt = conn.prepareCall(storedProcedureCallSql); + setBaseStatementInsertParams(perceroObject, userId, cstmt); + } catch(SQLException e1) { + e1.printStackTrace(); + } + log.error(e.getMessage(), e); + throw new ConnectorException(e); + } + + return result; + } + + + @Override + public T createObject(T perceroObject, String userId) + throws SyncException { + if ( !hasCreateAccess(BaseDataObject.toClassIdPair(perceroObject), userId) ) { + return null; + } + + long timeStart = System.currentTimeMillis(); + + int result = runInsertStoredProcedure(perceroObject, userId); + + long timeEnd = System.currentTimeMillis(); + long totalTime = timeEnd - timeStart; + if (totalTime > LONG_RUNNING_QUERY_TIME) { + log.warn("LONG RUNNING QUERY: " + totalTime + "ms\n" + "Insert ThresholdExceededNotification"); + } + + if (result > 0) { + return retrieveObject(BaseDataObject.toClassIdPair(perceroObject), userId, false); + } + else { + return null; + } + } + + protected int runInsertStoredProcedure(T perceroObject, String userId) throws ConnectorException { + int result = runStoredProcedure(perceroObject, userId, getInsertCallableStatementSql()); + return result; + } + + protected void setBaseStatementInsertParams(T perceroObject, String userId, PreparedStatement pstmt) throws SQLException { + // Do nothing + } + + public T updateObject(T perceroObject, + Map> changedFields, String userId) + throws SyncException { + if (!hasUpdateAccess(BaseDataObject.toClassIdPair(perceroObject), userId)) { + return null; + } + + long timeStart = System.currentTimeMillis(); + + int updateResult = runUpdateStoredProcedure(perceroObject, userId); + + long timeEnd = System.currentTimeMillis(); + long totalTime = timeEnd - timeStart; + if (totalTime > LONG_RUNNING_QUERY_TIME) { + log.warn("LONG RUNNING QUERY: " + totalTime + "ms\n" + "Insert ThresholdExceededNotification"); + } + + T result = null; + if (updateResult > 0) { + result = retrieveObject(BaseDataObject.toClassIdPair(perceroObject), userId, false); + } + + return result; + } + + protected int runUpdateStoredProcedure(T perceroObject, String userId) throws ConnectorException { + int result = runStoredProcedure(perceroObject, userId, getUpdateCallableStatementSql()); + return result; + } + + public Boolean deleteObject(ClassIDPair classIdPair, String userId) + throws SyncException { + if ( !hasDeleteAccess(classIdPair, userId) ) { + return false; + } + + long timeStart = System.currentTimeMillis(); + + int updateResult = runDeleteStoredProcedure(classIdPair, userId); + + long timeEnd = System.currentTimeMillis(); + long totalTime = timeEnd - timeStart; + if (totalTime > LONG_RUNNING_QUERY_TIME) { + log.warn("LONG RUNNING QUERY: " + totalTime + "ms\n" + "Insert ThresholdExceededNotification"); + } + + return (updateResult > 0); + } + + protected int runDeleteStoredProcedure(ClassIDPair classIdPair, String userId) throws ConnectorException { + int result = 0; + IConnectionFactory connectionFactory = getConnectionRegistry().getConnectionFactory(getConnectionFactoryName()); + try (Connection conn = connectionFactory.getConnection(); Statement statement = conn.createStatement()) { + CallableStatement cstmt = conn.prepareCall(getDeleteCallableStatementSql()); + cstmt.setString(1, classIdPair.getID()); + cstmt.setString(2, userId); + result = cstmt.executeUpdate(); + } catch (SQLException e) { + log.error(e.getMessage(), e); + throw new ConnectorException(e); + } + + return result; + } + + public void initializeStoredProcedures() throws ConnectorException { + // Do nothing. + } + +} \ No newline at end of file diff --git a/src/main/java/com/percero/serial/JsonUtils.java b/src/main/java/com/percero/serial/JsonUtils.java index feefad6..58e7400 100644 --- a/src/main/java/com/percero/serial/JsonUtils.java +++ b/src/main/java/com/percero/serial/JsonUtils.java @@ -2,8 +2,10 @@ import java.util.ArrayList; import java.util.Date; +import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; import org.apache.commons.codec.binary.Base64; import org.apache.log4j.Logger; @@ -13,6 +15,7 @@ import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import com.percero.agents.sync.metadata.MappedClass; +import com.percero.agents.sync.vo.IJsonObject; import com.percero.framework.vo.IPerceroObject; public class JsonUtils { @@ -222,4 +225,16 @@ public static List getJsonListPerceroObject(JsonObject return result; } + @SuppressWarnings("rawtypes") + static final Map iJsonObjectAssignableClasses = new HashMap(); + @SuppressWarnings("rawtypes") + public static boolean isClassAssignableFromIJsonObject(Class clazz) { + Boolean result = iJsonObjectAssignableClasses.get(clazz); + if (result == null) { + result = IJsonObject.class.isAssignableFrom(clazz); + iJsonObjectAssignableClasses.put(clazz, result); + } + return result; + } + } diff --git a/src/main/java/com/percero/util/MappedClassUtils.java b/src/main/java/com/percero/util/MappedClassUtils.java new file mode 100644 index 0000000..389906a --- /dev/null +++ b/src/main/java/com/percero/util/MappedClassUtils.java @@ -0,0 +1,91 @@ +package com.percero.util; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class MappedClassUtils { + + @SuppressWarnings("rawtypes") + private static final Map> classFieldsByClass = new ConcurrentHashMap>(); + + @SuppressWarnings("rawtypes") + public static List getClassFields(Class theClass) { + List fieldList = classFieldsByClass.get(theClass); + if (fieldList == null) { + fieldList = new ArrayList(); + classFieldsByClass.put(theClass, fieldList); + + Field[] theFields = theClass.getDeclaredFields(); + for(Field nextField : theFields) { + boolean isStatic = Modifier.STATIC == (nextField.getModifiers() & Modifier.STATIC); + if (!isStatic) + fieldList.add(nextField); + } + + if (theClass.getSuperclass() != null) + fieldList.addAll(getClassFields(theClass.getSuperclass())); + } + + return fieldList; + } + + @SuppressWarnings("rawtypes") + private static final Map> classGetterMethods = new ConcurrentHashMap>(); + + @SuppressWarnings("rawtypes") + private static Map retrieveClassMethods(Class theClass) { + Map classMethods = classGetterMethods.get(theClass); + if (classMethods == null) { + classMethods = new ConcurrentHashMap(); + classGetterMethods.put(theClass, classMethods); + + Method[] theMethods = theClass.getMethods(); + for(Method nextMethod : theMethods) { + // We are storing lower case name for easy compare + classMethods.put(nextMethod.getName().toLowerCase(), nextMethod); + } + } + + return classMethods; + } + + @SuppressWarnings("rawtypes") + public static Method getFieldGetters(Class theClass, Field theField) { + Method theMethod = null; + String theModifiedFieldName = theField.getName(); + if (theModifiedFieldName.indexOf("_") == 0) + theModifiedFieldName = theModifiedFieldName.substring(1); + + String getterMethodName = (new StringBuilder("get").append(theModifiedFieldName.toLowerCase())).toString(); + + Map methods = retrieveClassMethods(theClass); + if (methods != null && !methods.isEmpty()) { + theMethod = methods.get(getterMethodName); + } + + return theMethod; + } + + @SuppressWarnings("rawtypes") + public static Method getFieldSetters(Class theClass, Field theField) { + Method theMethod = null; + String theModifiedFieldName = theField.getName(); + if (theModifiedFieldName.indexOf("_") == 0) + theModifiedFieldName = theModifiedFieldName.substring(1); + + String setterMethodName = (new StringBuilder("set").append(theModifiedFieldName.toLowerCase())).toString(); + + Map methods = retrieveClassMethods(theClass); + if (methods != null && !methods.isEmpty()) { + theMethod = methods.get(setterMethodName); + } + + return theMethod; + } + +} diff --git a/src/main/resources/env.properties.sample b/src/main/resources/env.properties.sample index 102a49d..9e629d0 100644 --- a/src/main/resources/env.properties.sample +++ b/src/main/resources/env.properties.sample @@ -14,6 +14,7 @@ databaseAuth.password=root databaseAuth.hibernate.hbm2ddl.auto=update # Recommended setting for PROD # databaseAuth.hibernate.hbm2ddl.auto=validate +auth.basic.enabled=true #Anonymous Properties anonAuth.enabled=false diff --git a/src/main/resources/spring/basic_components_spring_config.xml b/src/main/resources/spring/basic_components_spring_config.xml index fe34a72..bb02bca 100644 --- a/src/main/resources/spring/basic_components_spring_config.xml +++ b/src/main/resources/spring/basic_components_spring_config.xml @@ -46,6 +46,7 @@ + @@ -135,6 +136,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -210,92 +257,11 @@ - - - - diff --git a/src/main/resources/spring/messageListener-spring-config.xml b/src/main/resources/spring/messageListener-spring-config.xml index 024bd01..8ebe1ae 100644 --- a/src/main/resources/spring/messageListener-spring-config.xml +++ b/src/main/resources/spring/messageListener-spring-config.xml @@ -18,52 +18,8 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/src/main/resources/spring/percero-spring-config.xml b/src/main/resources/spring/percero-spring-config.xml index 091f759..ba0d24c 100644 --- a/src/main/resources/spring/percero-spring-config.xml +++ b/src/main/resources/spring/percero-spring-config.xml @@ -17,53 +17,8 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/src/test/java/com/percero/agents/auth/services/TestAuthService.java b/src/test/java/com/percero/agents/auth/services/TestAuthService.java new file mode 100644 index 0000000..d69df05 --- /dev/null +++ b/src/test/java/com/percero/agents/auth/services/TestAuthService.java @@ -0,0 +1,217 @@ +package com.percero.agents.auth.services; + +import static org.junit.Assert.*; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.Mockito; + +import com.percero.agents.auth.vo.AuthProvider; +import com.percero.agents.auth.vo.ServiceUser; +import com.percero.agents.auth.vo.UserAccount; + +public class TestAuthService { + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + } + + AuthService authService; + ServiceUser facebookServiceUser; + ServiceUser googleServiceUser; + ServiceUser githubServiceUser; + ServiceUser linkedInServiceUser; + + @Before + public void setUp() throws Exception { + authService = Mockito.mock(AuthService.class); + authService.anonAuthEnabled = false; + + // Setup Facebook Helper + authService.facebookHelper = Mockito.mock(FacebookHelper.class); + facebookServiceUser = new ServiceUser(); + facebookServiceUser.setAuthProviderID(AuthProvider.FACEBOOK.name()); + Mockito.when(authService.facebookHelper.getServiceUser(Mockito.anyString(), Mockito.anyString())).thenReturn(facebookServiceUser); + + // Setup Google Helper + authService.googleHelper = Mockito.mock(GoogleHelper.class); + googleServiceUser = new ServiceUser(); + googleServiceUser.setAuthProviderID(AuthProvider.GOOGLE.name()); + Mockito.when(authService.googleHelper.authenticateAccessToken(Mockito.anyString(), Mockito.anyString(), Mockito.anyString())).thenReturn(googleServiceUser); + + // Setup Github Helper + authService.githubHelper = Mockito.mock(GitHubHelper.class); + githubServiceUser = new ServiceUser(); + githubServiceUser.setAuthProviderID(AuthProvider.GITHUB.name()); + Mockito.when(authService.githubHelper.getServiceUser(Mockito.anyString(), Mockito.anyString())).thenReturn(githubServiceUser); + + // Setup LinkedIn Helper + authService.linkedInHelper = Mockito.mock(LinkedInHelper.class); + linkedInServiceUser = new ServiceUser(); + linkedInServiceUser.setAuthProviderID(AuthProvider.LINKEDIN.name()); + Mockito.when(authService.linkedInHelper.getServiceUser(Mockito.anyString(), Mockito.anyString())).thenReturn(linkedInServiceUser); + + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void testGetServiceProviderServiceUserUserAccountString() { + Mockito.when(authService.getServiceProviderServiceUser(Mockito.any(UserAccount.class), Mockito.anyString())).thenCallRealMethod(); + Mockito.when(authService.getServiceProviderServiceUser(Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.anyString())).thenCallRealMethod(); + + UserAccount userAccount = new UserAccount(); + userAccount.setAccessToken("ACCESS_TOKEN"); + userAccount.setAccountId("ACCOUNT_ID"); + userAccount.setRefreshToken("REFRESH_TOKEN"); + + // authService.getServiceProviderServiceUser(userAccount, + // AuthProvider.FACEBOOK.name()) is just a wrapper function, so as long + // as it returns a ServiceUser it is valid. + ServiceUser resultingServiceUser = authService.getServiceProviderServiceUser(userAccount, AuthProvider.FACEBOOK.name()); + assertNotNull(resultingServiceUser); + assertSame(facebookServiceUser, resultingServiceUser); + assertEquals(AuthProvider.FACEBOOK.name(), resultingServiceUser.getAuthProviderID()); + } + + @Test + public void testGetServiceProviderServiceUserStringStringStringString() { + Mockito.when(authService.getServiceProviderServiceUser(Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.anyString())).thenCallRealMethod(); + + String accessToken = "ACCESS_TOKEN"; + String refreshToken = "REFRESH_TOKEN"; + String accountId = "ACCOUNT_ID"; + String authProviderID = "NONE"; + + // Test finding Auth Provider by name + + // FACEBOOK + authProviderID = "FacEBooK"; // Case should NOT matter. + ServiceUser resultingServiceUser = authService.getServiceProviderServiceUser(accessToken, refreshToken, accountId, authProviderID); + assertNotNull(resultingServiceUser); + assertSame(facebookServiceUser, resultingServiceUser); + assertEquals(authProviderID, resultingServiceUser.getAuthProviderID()); + + authProviderID = AuthProvider.FACEBOOK.name(); + resultingServiceUser = authService.getServiceProviderServiceUser(accessToken, refreshToken, accountId, authProviderID); + assertNotNull(resultingServiceUser); + assertSame(facebookServiceUser, resultingServiceUser); + assertEquals(authProviderID, resultingServiceUser.getAuthProviderID()); + + authProviderID = AuthProvider.FACEBOOK.toString(); + resultingServiceUser = authService.getServiceProviderServiceUser(accessToken, refreshToken, accountId, authProviderID); + assertNotNull(resultingServiceUser); + assertSame(facebookServiceUser, resultingServiceUser); + assertEquals(authProviderID, resultingServiceUser.getAuthProviderID()); + + // GOOGLE + authProviderID = "gOOgle"; // Case should NOT matter. + resultingServiceUser = authService.getServiceProviderServiceUser(accessToken, refreshToken, accountId, authProviderID); + assertNotNull(resultingServiceUser); + assertSame(googleServiceUser, resultingServiceUser); + assertEquals(authProviderID, resultingServiceUser.getAuthProviderID()); + + authProviderID = AuthProvider.GOOGLE.name(); + resultingServiceUser = authService.getServiceProviderServiceUser(accessToken, refreshToken, accountId, authProviderID); + assertNotNull(resultingServiceUser); + assertSame(googleServiceUser, resultingServiceUser); + assertEquals(authProviderID, resultingServiceUser.getAuthProviderID()); + + authProviderID = AuthProvider.GOOGLE.toString(); + resultingServiceUser = authService.getServiceProviderServiceUser(accessToken, refreshToken, accountId, authProviderID); + assertNotNull(resultingServiceUser); + assertSame(googleServiceUser, resultingServiceUser); + assertEquals(authProviderID, resultingServiceUser.getAuthProviderID()); + + // GITHUB + authProviderID = "gIThub"; // Case should NOT matter. + resultingServiceUser = authService.getServiceProviderServiceUser(accessToken, refreshToken, accountId, authProviderID); + assertNotNull(resultingServiceUser); + assertSame(githubServiceUser, resultingServiceUser); + assertEquals(authProviderID, resultingServiceUser.getAuthProviderID()); + + authProviderID = AuthProvider.GITHUB.name(); + resultingServiceUser = authService.getServiceProviderServiceUser(accessToken, refreshToken, accountId, authProviderID); + assertNotNull(resultingServiceUser); + assertSame(githubServiceUser, resultingServiceUser); + assertEquals(authProviderID, resultingServiceUser.getAuthProviderID()); + + authProviderID = AuthProvider.GITHUB.toString(); + resultingServiceUser = authService.getServiceProviderServiceUser(accessToken, refreshToken, accountId, authProviderID); + assertNotNull(resultingServiceUser); + assertSame(githubServiceUser, resultingServiceUser); + assertEquals(authProviderID, resultingServiceUser.getAuthProviderID()); + + // LINKEDIN + authProviderID = "lINKEDin"; // Case should NOT matter. + resultingServiceUser = authService.getServiceProviderServiceUser(accessToken, refreshToken, accountId, authProviderID); + assertNotNull(resultingServiceUser); + assertSame(linkedInServiceUser, resultingServiceUser); + assertEquals(authProviderID, resultingServiceUser.getAuthProviderID()); + + authProviderID = AuthProvider.LINKEDIN.name(); + resultingServiceUser = authService.getServiceProviderServiceUser(accessToken, refreshToken, accountId, authProviderID); + assertNotNull(resultingServiceUser); + assertSame(linkedInServiceUser, resultingServiceUser); + assertEquals(authProviderID, resultingServiceUser.getAuthProviderID()); + + authProviderID = AuthProvider.LINKEDIN.toString(); + resultingServiceUser = authService.getServiceProviderServiceUser(accessToken, refreshToken, accountId, authProviderID); + assertNotNull(resultingServiceUser); + assertSame(linkedInServiceUser, resultingServiceUser); + assertEquals(authProviderID, resultingServiceUser.getAuthProviderID()); + + // ANON + authService.anonAuthEnabled = false; + authService.anonAuthCode = null; + authProviderID = "aNOn"; // Case should NOT matter. + resultingServiceUser = authService.getServiceProviderServiceUser(accessToken, refreshToken, accountId, authProviderID); + assertNull(resultingServiceUser); + + authService.anonAuthEnabled = true; + authService.anonAuthCode = refreshToken; + authProviderID = "aNOn"; // Case should NOT matter. + resultingServiceUser = authService.getServiceProviderServiceUser(accessToken, refreshToken, accountId, authProviderID); + assertNotNull(resultingServiceUser); + assertEquals(authProviderID, resultingServiceUser.getAuthProviderID()); + + authProviderID = AuthProvider.ANON.name(); + resultingServiceUser = authService.getServiceProviderServiceUser(accessToken, refreshToken, accountId, authProviderID); + assertNotNull(resultingServiceUser); + assertEquals(authProviderID, resultingServiceUser.getAuthProviderID()); + + authProviderID = AuthProvider.ANON.toString(); + resultingServiceUser = authService.getServiceProviderServiceUser(accessToken, refreshToken, accountId, authProviderID); + assertNotNull(resultingServiceUser); + assertEquals(authProviderID, resultingServiceUser.getAuthProviderID()); + + authProviderID = AuthProvider.ANON.name(); + resultingServiceUser = authService.getServiceProviderServiceUser(accessToken, refreshToken, accountId, authProviderID); + assertNotNull(resultingServiceUser); + assertEquals(authProviderID, resultingServiceUser.getAuthProviderID()); + assertEquals(refreshToken, resultingServiceUser.getRefreshToken()); + + // INVALID + authService.anonAuthEnabled = true; + authService.anonAuthCode = refreshToken; + authProviderID = "InvALID"; // Case should NOT matter. + resultingServiceUser = authService.getServiceProviderServiceUser(accessToken, refreshToken, accountId, authProviderID); + assertNull(resultingServiceUser); + + authService.anonAuthEnabled = false; // ANON should not impact INVALID auth provider ID. + authService.anonAuthCode = null; + authProviderID = "InvALID"; // Case should NOT matter. + resultingServiceUser = authService.getServiceProviderServiceUser(accessToken, refreshToken, accountId, authProviderID); + assertNull(resultingServiceUser); + } + +} diff --git a/src/test/java/com/percero/agents/auth/services/TestAuthService2.java b/src/test/java/com/percero/agents/auth/services/TestAuthService2.java new file mode 100644 index 0000000..20a5541 --- /dev/null +++ b/src/test/java/com/percero/agents/auth/services/TestAuthService2.java @@ -0,0 +1,157 @@ +package com.percero.agents.auth.services; + +import static org.junit.Assert.*; + +import java.util.ArrayList; + +import org.hibernate.SessionFactory; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.Mockito; + +import com.percero.agents.auth.vo.AuthProviderResponse; +import com.percero.agents.auth.vo.AuthenticationRequest; +import com.percero.agents.auth.vo.AuthenticationResponse; +import com.percero.agents.auth.vo.ServiceUser; +import com.percero.agents.auth.vo.UserAccount; +import com.percero.agents.auth.vo.UserToken; +import com.percero.framework.bl.IManifest; + +public class TestAuthService2 { + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + } + + AuthService2 authService; + + @Before + public void setUp() throws Exception { + authService = Mockito.mock(AuthService2.class); + authService.authProviderRegistry = Mockito.mock(AuthProviderRegistry.class); + authService.manifest = Mockito.mock(IManifest.class); + authService.sessionFactoryAuth = Mockito.mock(SessionFactory.class); + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void testRegister_prePostHandlers() { + AuthenticationRequest request = new AuthenticationRequest(); + + Mockito.when(authService.register(Mockito.any(AuthenticationRequest.class))).thenCallRealMethod(); + + // Test invalid AuthProvider + Mockito.when(authService.authProviderRegistry.hasProvider(Mockito.anyString())).thenReturn(false); + try { + authService.register(request); + fail("Did not catch invalid Auth Provider"); + } catch(IllegalArgumentException e) { + // Do nothing. + } + + // Test Pre/Post Register Handlers + IAuthProvider authProvider = Mockito.mock(IAuthProvider.class); + AuthProviderResponse apResponse = new AuthProviderResponse(); + apResponse.authCode = BasicAuthCode.SUCCESS; + apResponse.serviceUser = new ServiceUser(); + Mockito.when(authProvider.register(Mockito.anyString())).thenReturn(apResponse); + Mockito.when(authService.authProviderRegistry.getProvider(Mockito.anyString())).thenReturn(authProvider); + + TestPreRegisterHandler preRegisterHandler = new TestPreRegisterHandler(); + authService.preRegisterHandlers = new ArrayList<>(); + authService.preRegisterHandlers.add(preRegisterHandler); + TestPostRegisterHandler postRegisterHandler = new TestPostRegisterHandler(); + authService.postRegisterHandlers = new ArrayList<>(); + authService.postRegisterHandlers.add(postRegisterHandler); + + Mockito.when(authService.authProviderRegistry.hasProvider(Mockito.anyString())).thenReturn(true); + request.setCredential("CREDENTIAL"); + authService.register(request); + assertEquals("PreRegisterHandler NOT called", 1, preRegisterHandler.myTimesCalled); + assertEquals("PostRegisterHandler NOT called", 1, postRegisterHandler.myTimesCalled); + } + + private class TestPreRegisterHandler implements IPreRegisterHandler { + int myTimesCalled = 0; + @Override + public void run(AuthenticationRequest request) throws AuthException { + myTimesCalled++; + } + } + + private class TestPostRegisterHandler implements IPostRegisterHandler { + int myTimesCalled = 0; + @Override + public AuthenticationResponse run(AuthenticationRequest request, AuthenticationResponse registerResponse, ServiceUser serviceUser) throws AuthException { + myTimesCalled++; + return registerResponse; + } + } + + @Test + public void testAuthenticate_prePostHandlers() { + AuthenticationRequest request = new AuthenticationRequest(); + Mockito.when(authService.authenticate(Mockito.any(AuthenticationRequest.class))).thenCallRealMethod(); + + // Test invalid AuthProvider + Mockito.when(authService.authProviderRegistry.hasProvider(Mockito.anyString())).thenReturn(false); + try { + authService.authenticate(request); + fail("Did not catch invalid Auth Provider"); + } catch(IllegalArgumentException e) { + // Do nothing. + } + + Mockito.when(authService.getOrCreateUserAccount(Mockito.any(ServiceUser.class), Mockito.any(IAuthProvider.class))).thenReturn(new UserAccount()); + Mockito.when(authService.loginUserAccount(Mockito.any(UserAccount.class), Mockito.anyString(), Mockito.anyString())).thenReturn(new UserToken()); + + // Test Pre/Post Authenticate Handlers + IAuthProvider authProvider = Mockito.mock(IAuthProvider.class); + AuthProviderResponse apResponse = new AuthProviderResponse(); + apResponse.authCode = BasicAuthCode.SUCCESS; + apResponse.serviceUser = new ServiceUser(); + Mockito.when(authProvider.authenticate(Mockito.anyString())).thenReturn(apResponse); + Mockito.when(authService.authProviderRegistry.getProvider(Mockito.anyString())).thenReturn(authProvider); + + TestPreAuthenticateHandler preAuthenticateHandler = new TestPreAuthenticateHandler(); + authService.preAuthenticateHandlers = new ArrayList<>(); + authService.preAuthenticateHandlers.add(preAuthenticateHandler); + TestPostAuthenticateHandler postAuthenticateHandler = new TestPostAuthenticateHandler(); + authService.postAuthenticateHandlers = new ArrayList<>(); + authService.postAuthenticateHandlers.add(postAuthenticateHandler); + + Mockito.when(authService.authProviderRegistry.hasProvider(Mockito.anyString())).thenReturn(true); + request.setCredential("CREDENTIAL"); + authService.authenticate(request); + assertEquals("PreAuthenticateHandler NOT called", 1, preAuthenticateHandler.myTimesCalled); + assertEquals("PostAuthenticateHandler NOT called", 1, postAuthenticateHandler.myTimesCalled); + } + + private class TestPreAuthenticateHandler implements IPreAuthenticateHandler { + int myTimesCalled = 0; + @Override + public void run(AuthenticationRequest request) throws AuthException { + myTimesCalled++; + } + } + + private class TestPostAuthenticateHandler implements IPostAuthenticateHandler { + int myTimesCalled = 0; + @Override + public AuthenticationResponse run(AuthenticationRequest request, AuthenticationResponse authenticateResponse, ServiceUser serviceUser) throws AuthException { + myTimesCalled++; + return authenticateResponse; + } + } + +} diff --git a/src/test/java/com/percero/agents/auth/services/TestBasicAuthProvider.java b/src/test/java/com/percero/agents/auth/services/TestBasicAuthProvider.java new file mode 100644 index 0000000..9ac84f2 --- /dev/null +++ b/src/test/java/com/percero/agents/auth/services/TestBasicAuthProvider.java @@ -0,0 +1,194 @@ +package com.percero.agents.auth.services; + +import static org.junit.Assert.*; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.Mockito; + +import com.percero.agents.auth.vo.AuthProviderResponse; +import com.percero.agents.auth.vo.BasicAuthCredential; +import com.percero.agents.auth.vo.ServiceUser; + +/** + * Tests the BasicAuthProvider + * + * @author Collin Brown + * + */ +public class TestBasicAuthProvider { + + private static final String VALID_CREDENTIAL_JSON = "{\"username\":\"tester3\",\"password\":\"password\",\"metadata\":{\"personType\":\"Parent\",\"firstName\":\"Test3\",\"lastName\":\"Er\",\"email\":\"test3@er.com\",\"dob\":\"2000-01-01\"}}"; + private static final String INVALID_CREDENTIAL_JSON = "\"username\" \"tester3\",\"password\" \"password\","; + private static final String VALID_CREDENTIAL_STRING = "tester3:password"; + private static final String INVALID_CREDENTIAL_STRING = "tester3 password"; + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + } + + BasicAuthProvider authProvider; + DatabaseHelper authDatabaseHelper; + + @Before + public void setUp() throws Exception { + + authDatabaseHelper = Mockito.mock(DatabaseHelper.class); + + authProvider = new BasicAuthProvider(authDatabaseHelper); + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void testAuthenticate() { + + // Test no ServiceUser + String credentialStr = VALID_CREDENTIAL_JSON; + Mockito.when(authDatabaseHelper.getServiceUser(Mockito.any(BasicAuthCredential.class))).thenReturn(null); + AuthProviderResponse response = authProvider.authenticate(credentialStr); + assertNotNull(response); + assertNull(response.serviceUser); + assertEquals("Unexpected auth code", BasicAuthCode.BAD_USER_PASS, response.authCode); + + // Test invalid credential JSON + credentialStr = INVALID_CREDENTIAL_JSON; + response = authProvider.authenticate(credentialStr); + assertNotNull(response); + assertEquals("Unexpected auth code", BasicAuthCode.BAD_USER_PASS, response.authCode); + assertNull(response.serviceUser); + + // Test invalid credential string + credentialStr = INVALID_CREDENTIAL_STRING; + response = authProvider.authenticate(credentialStr); + assertNotNull(response); + assertEquals("Unexpected auth code", BasicAuthCode.BAD_USER_PASS, response.authCode); + assertNull(response.serviceUser); + + // Test valid ServiceUser - JSON + credentialStr = VALID_CREDENTIAL_JSON; + Mockito.when(authDatabaseHelper.getServiceUser(Mockito.any(BasicAuthCredential.class))).thenReturn(new ServiceUser()); + response = authProvider.authenticate(credentialStr); + assertNotNull(response); + assertEquals("Unexpected auth code", BasicAuthCode.SUCCESS, response.authCode); + assertNotNull(response.serviceUser); + assertEquals("Unexpected AuthProvider ID", BasicAuthProvider.ID, response.serviceUser.getAuthProviderID()); + + // Test valid ServiceUser - String + credentialStr = VALID_CREDENTIAL_STRING; + Mockito.when(authDatabaseHelper.getServiceUser(Mockito.any(BasicAuthCredential.class))).thenReturn(new ServiceUser()); + response = authProvider.authenticate(credentialStr); + assertNotNull(response); + assertEquals("Unexpected auth code", BasicAuthCode.SUCCESS, response.authCode); + assertNotNull(response.serviceUser); + assertEquals("Unexpected AuthProvider ID", BasicAuthProvider.ID, response.serviceUser.getAuthProviderID()); + } + + @Test + public void testRegiser() { + String credentialStr = VALID_CREDENTIAL_JSON; + + // Test no ServiceUser + try { + Mockito.when(authDatabaseHelper.registerUser(Mockito.any(BasicAuthCredential.class), Mockito.anyString())).thenReturn(null); + } catch (AuthException e) { + // Do nothing. + } + AuthProviderResponse response = authProvider.register(credentialStr); + assertNotNull(response); + assertEquals("Unexpected auth code", BasicAuthCode.FAILURE, response.authCode); + + // Test invalid credential JSON + credentialStr = INVALID_CREDENTIAL_JSON; + response = authProvider.register(credentialStr); + assertNotNull(response); + assertEquals("Unexpected auth code", BasicAuthCode.BAD_USER_PASS, response.authCode); + assertNull(response.serviceUser); + + // Test invalid credential string + credentialStr = INVALID_CREDENTIAL_STRING; + response = authProvider.register(credentialStr); + assertNotNull(response); + assertEquals("Unexpected auth code", BasicAuthCode.BAD_USER_PASS, response.authCode); + assertNull(response.serviceUser); + + // Test valid ServiceUser + credentialStr = VALID_CREDENTIAL_JSON; + try { + Mockito.when(authDatabaseHelper.registerUser(Mockito.any(BasicAuthCredential.class), Mockito.anyString())).thenReturn(new ServiceUser()); + } catch (AuthException e) { + // Do nothing. + } + response = authProvider.register(credentialStr); + assertNotNull(response); + assertEquals("Unexpected auth code", BasicAuthCode.SUCCESS, response.authCode); + assertNotNull(response.serviceUser); + assertEquals("Unexpected AuthProvider ID", BasicAuthProvider.ID, response.serviceUser.getAuthProviderID()); + } + + @Test + public void testException_duplicateUserName() { + String credentialStr = "{\"username\":\"tester3\",\"password\":\"password\",\"metadata\":{\"personType\":\"Parent\",\"firstName\":\"Test3\",\"lastName\":\"Er\",\"email\":\"test3@er.com\",\"dob\":\"2000-01-01\"}}"; + + try { + Mockito.when(authDatabaseHelper.registerUser(Mockito.any(BasicAuthCredential.class), Mockito.anyString())).thenThrow(new AuthException("", AuthException.DUPLICATE_USER_NAME)); + } catch (AuthException e) { + // Do nothing. + } + AuthProviderResponse response = authProvider.register(credentialStr); + assertNotNull(response); + assertEquals("Unexpected auth code", BasicAuthCode.DUPLICATE_USER_NAME, response.authCode); + } + + @Test + public void testException_dataError() { + String credentialStr = "{\"username\":\"tester3\",\"password\":\"password\",\"metadata\":{\"personType\":\"Parent\",\"firstName\":\"Test3\",\"lastName\":\"Er\",\"email\":\"test3@er.com\",\"dob\":\"2000-01-01\"}}"; + + try { + Mockito.when(authDatabaseHelper.registerUser(Mockito.any(BasicAuthCredential.class), Mockito.anyString())).thenThrow(new AuthException("", AuthException.DATA_ERROR)); + } catch (AuthException e) { + // Do nothing. + } + AuthProviderResponse response = authProvider.register(credentialStr); + assertNotNull(response); + assertEquals("Unexpected auth code", BasicAuthCode.FAILURE, response.authCode); + } + + @Test + public void testException_invalidUserIdentifier() { + String credentialStr = "{\"username\":\"tester3\",\"password\":\"password\",\"metadata\":{\"personType\":\"Parent\",\"firstName\":\"Test3\",\"lastName\":\"Er\",\"email\":\"test3@er.com\",\"dob\":\"2000-01-01\"}}"; + + try { + Mockito.when(authDatabaseHelper.registerUser(Mockito.any(BasicAuthCredential.class), Mockito.anyString())).thenThrow(new AuthException("", AuthException.INVALID_USER_IDENTIFIER)); + } catch (AuthException e) { + // Do nothing. + } + AuthProviderResponse response = authProvider.register(credentialStr); + assertNotNull(response); + assertEquals("Unexpected auth code", BasicAuthCode.FAILURE, response.authCode); + } + + @Test + public void testException_invalidUserPassword() { + String credentialStr = "{\"username\":\"tester3\",\"password\":\"password\",\"metadata\":{\"personType\":\"Parent\",\"firstName\":\"Test3\",\"lastName\":\"Er\",\"email\":\"test3@er.com\",\"dob\":\"2000-01-01\"}}"; + + try { + Mockito.when(authDatabaseHelper.registerUser(Mockito.any(BasicAuthCredential.class), Mockito.anyString())).thenThrow(new AuthException("", AuthException.INVALID_USER_PASSWORD)); + } catch (AuthException e) { + // Do nothing. + } + AuthProviderResponse response = authProvider.register(credentialStr); + assertNotNull(response); + assertEquals("Unexpected auth code", BasicAuthCode.BAD_USER_PASS, response.authCode); + } + +} diff --git a/src/test/java/com/percero/agents/auth/services/TestBasicAuthProviderFactory.java b/src/test/java/com/percero/agents/auth/services/TestBasicAuthProviderFactory.java new file mode 100644 index 0000000..62e4f9f --- /dev/null +++ b/src/test/java/com/percero/agents/auth/services/TestBasicAuthProviderFactory.java @@ -0,0 +1,65 @@ +package com.percero.agents.auth.services; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.Mockito; + +/** + * Tests the Basic Auth Provider Factory class + * + * @author Collin Brown + * + */ +public class TestBasicAuthProviderFactory { + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + } + + AuthProviderRegistry authProviderRegistry; + DatabaseHelper authDatabaseHelper; + BasicAuthProviderFactory factory; + + @Before + public void setUp() throws Exception { + + authDatabaseHelper = Mockito.mock(DatabaseHelper.class); + + authProviderRegistry = Mockito.mock(AuthProviderRegistry.class); + + factory = new BasicAuthProviderFactory(); + factory.authDatabaseHelper = authDatabaseHelper; + factory.authProviderRegistry = authProviderRegistry; + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void testInit() { + // Basic Auth DISABLED - null + factory.basicAuthEnabled = null; + factory.init(); + Mockito.verify(authProviderRegistry, Mockito.never()).addProvider(Mockito.any(IAuthProvider.class)); + + // Basic Auth DISABLED - false + factory.basicAuthEnabled = false; + factory.init(); + Mockito.verify(authProviderRegistry, Mockito.never()).addProvider(Mockito.any(IAuthProvider.class)); + + // Basic Auth ENABLED + factory.basicAuthEnabled = true; + factory.init(); + Mockito.verify(authProviderRegistry, Mockito.atLeastOnce()).addProvider(Mockito.any(IAuthProvider.class)); + + } + +} diff --git a/src/test/java/com/percero/agents/auth/services/TestDatabaseHelper.java b/src/test/java/com/percero/agents/auth/services/TestDatabaseHelper.java new file mode 100644 index 0000000..8c0b6bd --- /dev/null +++ b/src/test/java/com/percero/agents/auth/services/TestDatabaseHelper.java @@ -0,0 +1,169 @@ +package com.percero.agents.auth.services; + +import static org.junit.Assert.fail; + +import java.util.ArrayList; +import java.util.List; + +import org.hibernate.Query; +import org.hibernate.SQLQuery; +import org.hibernate.SessionFactory; +import org.hibernate.Transaction; +import org.hibernate.classic.Session; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.Mockito; + +import com.percero.agents.auth.vo.BasicAuthCredential; +import com.percero.agents.auth.vo.ServiceUser; +import com.percero.agents.auth.vo.UserIdentifier; + +public class TestDatabaseHelper { + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + } + + DatabaseHelper databaseHelper = null; + Query query = null; + SQLQuery sqlQuery = null; + Session session = null; + Transaction tx = null; + + @Before + public void setUp() throws Exception { + databaseHelper = Mockito.mock(DatabaseHelper.class); + databaseHelper.sessionFactoryAuth = Mockito.mock(SessionFactory.class); + + session = Mockito.mock(Session.class); + query = Mockito.mock(Query.class); + sqlQuery = Mockito.mock(SQLQuery.class); + tx = Mockito.mock(Transaction.class); + + Mockito.when(databaseHelper.sessionFactoryAuth.openSession()).thenReturn(session); + Mockito.when(session.createQuery(Mockito.anyString())).thenReturn(query); + Mockito.when(session.createSQLQuery(Mockito.anyString())).thenReturn(sqlQuery); + Mockito.when(session.beginTransaction()).thenReturn(tx); + + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void testRegisterUserExceptionsAndSuccess() { + + try { + Mockito.when(databaseHelper.registerUser(Mockito.any(BasicAuthCredential.class), Mockito.anyString())).thenCallRealMethod(); + } catch (AuthException e1) { + // Do nothing here. + } + + try { + Mockito.when(databaseHelper.registerUser(Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.anyString())).thenCallRealMethod(); + } catch (AuthException e1) { + // Do nothing here. + } + + Mockito.when(databaseHelper.getServiceUser(Mockito.anyString(), Mockito.anyString())).thenReturn(new ServiceUser()); + + ServiceUser serviceUser = null; + String paradigm = "TEST"; + + // Test missing data. + BasicAuthCredential credential = new BasicAuthCredential(); + try { + serviceUser = databaseHelper.registerUser(credential, paradigm); + // If we get here, then we didn't catch the missing data. + fail("Did not catch missing data"); + } catch (AuthException e) { + // Make sure we have the appropriate failure reported. + if (!AuthException.INVALID_DATA.equalsIgnoreCase(e.getDetail())) { + fail("Incorrect failure. Should be " + AuthException.INVALID_DATA + ", but was " + e.getDetail() + " instead"); + } + } + + // Test duplicate user name. + List dupUserNamesList = new ArrayList(); + dupUserNamesList.add(new Object()); + Mockito.when(query.list()).thenReturn(dupUserNamesList); + + credential.setUsername("USER"); + credential.setPassword("PASSWORD"); + try { + serviceUser = databaseHelper.registerUser(credential, paradigm); + // If we get here, then we didn't catch the missing data. + fail("Did not catch missing data"); + } catch (AuthException e) { + // Make sure we have the appropriate failure reported. + if (!AuthException.DUPLICATE_USER_NAME.equalsIgnoreCase(e.getDetail())) { + fail("Incorrect failure. Should be " + AuthException.DUPLICATE_USER_NAME + ", but was " + e.getDetail() + " instead"); + } + } + + // Test invalid User Identifier. + dupUserNamesList.clear(); + Mockito.when(query.list()).thenReturn(dupUserNamesList); + Mockito.when(query.uniqueResult()).thenReturn(null); + + credential.setUsername("USER"); + credential.setPassword("PASSWORD"); + try { + serviceUser = databaseHelper.registerUser(credential, paradigm); + // If we get here, then we didn't catch the missing data. + fail("Did not catch missing data"); + } catch (AuthException e) { + // Make sure we have the appropriate failure reported. + if (!AuthException.INVALID_USER_IDENTIFIER.equalsIgnoreCase(e.getDetail())) { + fail("Incorrect failure. Should be " + AuthException.INVALID_USER_IDENTIFIER + ", but was " + e.getDetail() + " instead"); + } + } + + // Test invalid User Password. + UserIdentifier userIdentifier = new UserIdentifier(); + dupUserNamesList.clear(); + Mockito.when(query.list()).thenReturn(dupUserNamesList); + Mockito.when(query.uniqueResult()).thenReturn(userIdentifier); + Mockito.when(sqlQuery.executeUpdate()).thenReturn(0); + + credential.setUsername("USER"); + credential.setPassword("PASSWORD"); + try { + serviceUser = databaseHelper.registerUser(credential, paradigm); + // If we get here, then we didn't catch the missing data. + fail("Did not catch missing data"); + } catch (AuthException e) { + // Make sure we have the appropriate failure reported. + if (!AuthException.INVALID_USER_PASSWORD.equalsIgnoreCase(e.getDetail())) { + fail("Incorrect failure. Should be " + AuthException.INVALID_USER_PASSWORD + ", but was " + e.getDetail() + " instead"); + } + } + + // Test Valid User. + dupUserNamesList.clear(); + Mockito.when(query.list()).thenReturn(dupUserNamesList); + Mockito.when(sqlQuery.executeUpdate()).thenReturn(1); + + credential.setUsername("USER"); + credential.setPassword("PASSWORD"); + try { + serviceUser = databaseHelper.registerUser(credential, paradigm); + + if (serviceUser == null) { + // If we get here, then we didn't catch the missing data. + fail("Did not create ServiceUser"); + } + } catch (AuthException e) { + fail("Failed to create User: " + e.getDetail()); + } + } + +} diff --git a/src/test/java/com/percero/agents/sync/jobs/UpdateTableProcessorTest.java b/src/test/java/com/percero/agents/sync/jobs/UpdateTableProcessorTest.java index 1d57a6f..d5e8d34 100644 --- a/src/test/java/com/percero/agents/sync/jobs/UpdateTableProcessorTest.java +++ b/src/test/java/com/percero/agents/sync/jobs/UpdateTableProcessorTest.java @@ -25,8 +25,6 @@ @ContextConfiguration(locations = { "classpath:spring/update_table_processor.xml" }) public class UpdateTableProcessorTest { - @Autowired - UpdateTableConnectionFactory connectionFactory; @Autowired UpdateTablePoller poller; @Autowired @@ -34,10 +32,22 @@ public class UpdateTableProcessorTest { @Autowired AuthUtil authUtil; + UpdateTableConnectionFactory connectionFactory = null; String tableName = "update_table"; @Before public void before() throws Exception{ + + connectionFactory = new UpdateTableConnectionFactory(); + connectionFactory.setDriverClassName("com.mysql.jdbc.Driver"); + connectionFactory.setJdbcUrl("jdbc:mysql://localhost:3306/as_Example?autoReconnect=true"); + connectionFactory.setUsername("root"); + connectionFactory.setPassword("root"); + connectionFactory.setRowIdColumnName("rowID"); + connectionFactory.setLockIdColumnName("lockID"); + connectionFactory.setLockDateColumnName("lockDate"); + connectionFactory.setTimestampColumnName("timestamp"); + // Disable the poller so it doesn't step on our toes poller.enabled = false; cleanerUtil.cleanAll(); @@ -50,28 +60,28 @@ public void before() throws Exception{ } } - @Test + @SuppressWarnings("rawtypes") + @Test public void getClassForTableName_NoTableAnnotation() throws Exception{ - UpdateTableConnectionFactory connectionFactory = new UpdateTableConnectionFactory(); UpdateTableProcessor processor = poller.getProcessor(connectionFactory, tableName); - List clazz = processor.getClassesForTableName("Email"); - Assert.assertEquals(Email.class, clazz); + List clazzes = processor.getClassesForTableName("Email"); + Assert.assertTrue(clazzes.contains(Email.class)); } + @SuppressWarnings("rawtypes") @Test public void getClassForTableName_TableAnnotation() throws Exception{ - UpdateTableConnectionFactory connectionFactory = new UpdateTableConnectionFactory(); UpdateTableProcessor processor = poller.getProcessor(connectionFactory, tableName); - List clazz = processor.getClassesForTableName("Person"); - Assert.assertEquals(Person.class, clazz); + List clazzes = processor.getClassesForTableName("Person"); + Assert.assertTrue(clazzes.contains(Person.class)); } + @SuppressWarnings("rawtypes") @Test public void getClassForTableName_NotFound() throws Exception{ - UpdateTableConnectionFactory connectionFactory = new UpdateTableConnectionFactory(); UpdateTableProcessor processor = poller.getProcessor(connectionFactory, tableName); - List clazz = processor.getClassesForTableName("NotAnEntity"); - Assert.assertNull(clazz); + List clazzes = processor.getClassesForTableName("NotAnEntity"); + Assert.assertTrue(clazzes.isEmpty()); } // Shared setup method @@ -90,7 +100,6 @@ public void setupThreeRowsInUpdateTable() throws SQLException{ @Test public void getRow() throws Exception { setupThreeRowsInUpdateTable(); - UpdateTableConnectionFactory connectionFactory = new UpdateTableConnectionFactory(); UpdateTableProcessor processor = poller.getProcessor(connectionFactory, tableName); List rows = processor.getRows(1); UpdateTableRow row = rows.get(0); @@ -116,7 +125,6 @@ public void getRow() throws Exception { public void processMultipleRows() throws Exception { setupThreeRowsInUpdateTable(); - UpdateTableConnectionFactory connectionFactory = new UpdateTableConnectionFactory(); UpdateTableProcessor processor = poller.getProcessor(connectionFactory, tableName); // ProcessorResult result = processor.run(); // Assert.assertEquals(3, result.getTotal()); diff --git a/src/test/java/com/percero/agents/sync/metadata/TestMappedClass.java b/src/test/java/com/percero/agents/sync/metadata/TestMappedClass.java new file mode 100644 index 0000000..37a199b --- /dev/null +++ b/src/test/java/com/percero/agents/sync/metadata/TestMappedClass.java @@ -0,0 +1,217 @@ +package com.percero.agents.sync.metadata; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.Map; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import com.percero.agents.sync.metadata.IMappedClassManager; +import com.percero.agents.sync.metadata.MappedClass; +import com.percero.agents.sync.metadata.MappedClassManagerFactory; +import com.percero.agents.sync.metadata.MappedField; +import com.percero.agents.sync.metadata.MappedFieldPerceroObject; +import com.percero.example.CountryPermit; +import com.percero.example.ExampleManifest; +import com.percero.example.Person; +import com.percero.example.PostalAddress; +import com.percero.example.ShippingAddress; +import com.percero.framework.bl.IManifest; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = { "classpath:spring/percero-spring-config.xml" }) +public class TestMappedClass { + + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + } + + @Before + public void setUp() throws Exception { + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void testProcessManifest() { + IManifest manifest = new ExampleManifest(); + MappedClass.processManifest(manifest); + + IMappedClassManager mcm = MappedClassManagerFactory.getMappedClassManager(); + MappedClass personMappedClass = mcm.getMappedClassByClassName(Person.class.getCanonicalName()); + assertNotNull(personMappedClass); + + MappedClass countryPermitMappedClass = mcm.getMappedClassByClassName(CountryPermit.class.getCanonicalName()); + assertNotNull(countryPermitMappedClass); + + // Check one-to-one relationship on class. + MappedClass postalAddressMappedClass = mcm.getMappedClassByClassName(PostalAddress.class.getCanonicalName()); + assertNotNull(postalAddressMappedClass); + + Map nulledOnRemoveFieldReferences = postalAddressMappedClass.getNulledOnRemoveFieldReferences(); + assertNotNull(nulledOnRemoveFieldReferences); + assertEquals(1, nulledOnRemoveFieldReferences.size()); + + Map.Entry nextEntry = nulledOnRemoveFieldReferences.entrySet().iterator().next(); + MappedField nextKey = nextEntry.getKey(); + assertNotNull(nextKey); + assertTrue(nextKey instanceof MappedFieldPerceroObject); + assertEquals("issuerAddress", nextKey.getField().getName()); + assertNotNull(nextKey.getReverseMappedField()); + + MappedField nextValue = nextEntry.getValue(); + assertNotNull(nextKey); + assertTrue(nextValue instanceof MappedFieldPerceroObject); + assertEquals("countryPermit", nextValue.getField().getName()); + assertNotNull(nextValue.getReverseMappedField()); + + // Check one-to-one relationship on parent class. + MappedClass shippingAddressMappedClass = mcm.getMappedClassByClassName(ShippingAddress.class.getCanonicalName()); + assertNotNull(shippingAddressMappedClass); + assertSame(postalAddressMappedClass, shippingAddressMappedClass.parentMappedClass); + assertTrue(postalAddressMappedClass.getChildMappedClasses().contains(shippingAddressMappedClass)); + + nulledOnRemoveFieldReferences = shippingAddressMappedClass.getNulledOnRemoveFieldReferences(); + assertNotNull(nulledOnRemoveFieldReferences); + assertEquals(1, nulledOnRemoveFieldReferences.size()); + + nextEntry = nulledOnRemoveFieldReferences.entrySet().iterator().next(); + nextKey = nextEntry.getKey(); + assertNotNull(nextKey); + assertTrue(nextKey instanceof MappedFieldPerceroObject); + assertEquals("issuerAddress", nextKey.getField().getName()); + assertNotNull(nextKey.getReverseMappedField()); + + nextValue = nextEntry.getValue(); + assertNotNull(nextKey); + assertTrue(nextValue instanceof MappedFieldPerceroObject); + assertEquals("countryPermit", nextValue.getField().getName()); + assertNotNull(nextValue.getReverseMappedField()); + + assertSame(postalAddressMappedClass, shippingAddressMappedClass.parentMappedClass); + assertTrue(postalAddressMappedClass.getChildMappedClasses().contains(shippingAddressMappedClass)); + + // Checking inheritance. + for(MappedClass mc : mcm.getAllMappedClasses()) { + for(MappedClass childClass : mc.getChildMappedClasses()) { + assertTrue(mc.toManyFields.size() <= childClass.toManyFields.size()); + for(MappedField nextToManyMappedField : mc.toManyFields) { + boolean matchFound = false; + for(MappedField nextToManyMappedField2 : childClass.toOneFields) { + if (nextToManyMappedField.getField().equals(nextToManyMappedField2.getField())) { + matchFound = true; + assertSame(nextToManyMappedField.getReverseMappedField(), nextToManyMappedField2.getReverseMappedField()); + break; + } + } + + assertTrue(matchFound); + } + assertTrue(mc.toOneFields.size() <= childClass.toOneFields.size()); + for(MappedField nextToOneMappedField : mc.toOneFields) { + boolean matchFound = false; + for(MappedField nextToOneMappedField2 : childClass.toOneFields) { + if (nextToOneMappedField.getField().equals(nextToOneMappedField2.getField())) { + matchFound = true; + assertSame(nextToOneMappedField.getReverseMappedField(), nextToOneMappedField2.getReverseMappedField()); + break; + } + } + + assertTrue(matchFound); + } + + assertEquals(shippingAddressMappedClass.externalizableFields.size(), postalAddressMappedClass.externalizableFields.size()); + for(MappedField nextExternalizableField : postalAddressMappedClass.externalizableFields) { + boolean matchFound = false; + for(MappedField nextExternalizableField2 : shippingAddressMappedClass.externalizableFields) { + if (nextExternalizableField.getField().equals(nextExternalizableField2.getField())) { + matchFound = true; + assertSame(nextExternalizableField.getReverseMappedField(), nextExternalizableField2.getReverseMappedField()); + break; + } + } + + assertTrue(matchFound); + } + + assertEquals(shippingAddressMappedClass.propertyFields.size(), postalAddressMappedClass.propertyFields.size()); + for(MappedField nextPropertyField : postalAddressMappedClass.propertyFields) { + boolean matchFound = false; + for(MappedField nextPropertyField2 : shippingAddressMappedClass.propertyFields) { + if (nextPropertyField.getField().equals(nextPropertyField2.getField())) { + matchFound = true; + assertSame(nextPropertyField.getReverseMappedField(), nextPropertyField2.getReverseMappedField()); + break; + } + } + + assertTrue(matchFound); + } + } + } + } + + @Test + public void testHandleAnnotation_PropertyInterfaces() { + System.out.println("Not yet implemented"); + } + + @Test + public void testHandleAnnotation_PropertyInterface() { + System.out.println("Not yet implemented"); + } + + @Test + public void testHandleAnnotation_OneToOne() { + System.out.println("Not yet implemented"); + } + + @Test + public void testHandleAnnotation_ManyToOne() { + System.out.println("Not yet implemented"); + } + + @Test + public void testHandleAnnotation_OneToMany() { + System.out.println("Not yet implemented"); + } + + @Test + public void testHandleAnnotation_Id() { + System.out.println("Not yet implemented"); + } + + @Test + public void testHandleAnnotation_JoinColumn() { + System.out.println("Not yet implemented"); + } + + @Test + public void testHandleAnnotation_Column() { + System.out.println("Not yet implemented"); + } + + @Test + public void testHandleAnnotation_AccessRights() { + System.out.println("Not yet implemented"); + } +} diff --git a/src/test/java/com/percero/agents/sync/services/TestDaoDataProvider.java b/src/test/java/com/percero/agents/sync/services/TestDaoDataProvider.java new file mode 100644 index 0000000..e3f386e --- /dev/null +++ b/src/test/java/com/percero/agents/sync/services/TestDaoDataProvider.java @@ -0,0 +1,139 @@ +package com.percero.agents.sync.services; + +import static org.junit.Assert.*; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +public class TestDaoDataProvider { + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + } + + @Before + public void setUp() throws Exception { + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void testFindByIdClassIDPairString() { + System.out.println("Not yet implemented"); + } + + @Test + public void testFindByIdClassIDPairStringBoolean() { + System.out.println("Not yet implemented"); + } + + @Test + public void testFindByIdClassIDPairStringBooleanBoolean() { + System.out.println("Not yet implemented"); + } + + @Test + public void testRetrieveCachedObject() { + System.out.println("Not yet implemented"); + } + + @Test + public void testCreateFromJson() { + System.out.println("Not yet implemented"); + } + + @Test + public void testFindByIdsClassIDPairsString() { + System.out.println("Not yet implemented"); + } + + @Test + public void testFindByIdsClassIDPairsStringBoolean() { + System.out.println("Not yet implemented"); + } + + @Test + public void testFindByExample() { + System.out.println("Not yet implemented"); + } + + @Test + public void testCreateObject() { + System.out.println("Not yet implemented"); + } + + @Test + public void testPutObject() { + System.out.println("Not yet implemented"); + } + + @Test + public void testHandleUpdatedClassIdPair() { + System.out.println("Not yet implemented"); + } + + @Test + public void testFindPerceroObjectInList() { + System.out.println("Not yet implemented"); + } + + @Test + public void testDeleteObject() { + System.out.println("Not yet implemented"); + } + + @Test + public void testGetChangedMappedFieldsIPerceroObject() { + System.out.println("Not yet implemented"); + } + + @Test + public void testGetChangedMappedFieldsIPerceroObjectBoolean() { + System.out.println("Not yet implemented"); + } + + @Test + public void testGetChangedMappedFieldsIPerceroObjectIPerceroObjectBoolean() { + System.out.println("Not yet implemented"); + } + + @Test + public void testFindAllRelatedObjects() { + System.out.println("Not yet implemented"); + } + + @Test + public void testIsMappedFieldInChangedFields() { + System.out.println("Not yet implemented"); + } + + @Test + public void testPopulateToManyRelationships() { + System.out.println("Not yet implemented"); + } + + @Test + public void testOverwriteToManyRelationships() { + System.out.println("Not yet implemented"); + } + + @Test + public void testPopulateToOneRelationships() { + System.out.println("Not yet implemented"); + } + + @Test + public void testOverwriteToOneRelationships() { + System.out.println("Not yet implemented"); + } + +} diff --git a/src/test/java/com/percero/agents/sync/services/TestSyncAgentService.java b/src/test/java/com/percero/agents/sync/services/TestSyncAgentService.java new file mode 100644 index 0000000..9314297 --- /dev/null +++ b/src/test/java/com/percero/agents/sync/services/TestSyncAgentService.java @@ -0,0 +1,96 @@ +package com.percero.agents.sync.services; + +import static org.junit.Assert.assertTrue; + +import java.util.Collection; +import java.util.HashSet; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import com.percero.agents.sync.access.IAccessManager; +import com.percero.agents.sync.helpers.PostDeleteHelper; +import com.percero.agents.sync.vo.ClassIDPair; +import com.percero.example.Person; +import com.percero.framework.vo.IPerceroObject; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = { "classpath:spring/percero-spring-config.xml" }) +public class TestSyncAgentService { + +// @Autowired +// SyncAgentService syncAgentService; + + SyncAgentService mockService = null; + IDataProvider dataProvider = null; + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + } + + @Before + public void setUp() throws Exception { + mockService = Mockito.mock(SyncAgentService.class); + + mockService.accessManager = Mockito.mock(IAccessManager.class); + Mockito.when(mockService.accessManager.getClientUserId(Mockito.anyString())).thenReturn("USER_ID"); + + mockService.dataProviderManager = Mockito.mock(IDataProviderManager.class); + dataProvider = Mockito.mock(IDataProvider.class); + Mockito.when(mockService.dataProviderManager.getDataProviderByName(Mockito.anyString())).thenReturn(dataProvider); + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void testSystemDeleteObjectClassIDPairStringBoolean() throws Exception { + Mockito.when(mockService.systemDeleteObject(Mockito.any(ClassIDPair.class), Mockito.anyString(), Mockito.anyBoolean())).thenCallRealMethod(); + mockService.systemDeleteObject(new ClassIDPair("1", Person.class.getCanonicalName()), "clientId", true); + Mockito.verify(mockService, Mockito.times(1)).systemDeleteObject(Mockito.any(ClassIDPair.class), Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyCollection()); + } + + @Test + public void testSystemDeleteObjectClassIDPairStringBooleanCollectionOfClassIDPair() throws Exception { + Mockito.when(mockService.systemDeleteObject(Mockito.any(ClassIDPair.class), Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyCollection())).thenCallRealMethod(); + boolean result = mockService.systemDeleteObject(null, "clientId", true, null); + + Collection deletedObjects = new HashSet(1); + deletedObjects.add(new ClassIDPair("1", Person.class.getCanonicalName())); + result = mockService.systemDeleteObject(new ClassIDPair("1", Person.class.getCanonicalName()), "clientId", true, deletedObjects); + assertTrue(result); + + // If object does NOT exist, then it should just be ignored. + deletedObjects.clear(); + Mockito.when(dataProvider.findById(Mockito.any(ClassIDPair.class), Mockito.anyString())).thenReturn(null); + result = mockService.systemDeleteObject(new ClassIDPair("1", Person.class.getCanonicalName()), "clientId", true, deletedObjects); + assertTrue(result); + + Person person = new Person(); + Mockito.when(dataProvider.findById(Mockito.any(ClassIDPair.class), Mockito.anyString())).thenReturn(person); + Mockito.when(dataProvider.deleteObject(Mockito.any(ClassIDPair.class), Mockito.anyString())).thenReturn(true); + mockService.postDeleteHelper = Mockito.mock(PostDeleteHelper.class); + + ClassIDPair classIdPair = new ClassIDPair("1", Person.class.getCanonicalName()); + result = mockService.systemDeleteObject(classIdPair, "clientId", true, deletedObjects); + assertTrue(result); + assertTrue(deletedObjects.contains(classIdPair)); + } + + @Test + public void test_handleUpdateObject_ChangedFields() { + System.out.println("Not yet implemented"); + } +} diff --git a/src/test/java/com/percero/amqp/TestRabbitMQPushSyncHelper.java b/src/test/java/com/percero/amqp/TestRabbitMQPushSyncHelper.java new file mode 100644 index 0000000..66ca5e7 --- /dev/null +++ b/src/test/java/com/percero/amqp/TestRabbitMQPushSyncHelper.java @@ -0,0 +1,409 @@ +package com.percero.amqp; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.Credentials; +import org.codehaus.jackson.map.ObjectMapper; +import org.joda.time.Duration; +import org.joda.time.Instant; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.springframework.amqp.core.AmqpTemplate; +import org.springframework.amqp.core.Message; + +import com.google.gson.JsonSyntaxException; +import com.percero.agents.sync.access.IAccessManager; +import com.percero.agents.sync.datastore.ICacheDataStore; + +public class TestRabbitMQPushSyncHelper { + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + } + + RabbitMQPushSyncHelper pushSyncHelper; + final String jsonMessages = "[{\"memory\":21920,\"messages\":0,\"messages_details\":{\"rate\":0.0},\"messages_ready\":0,\"messages_ready_details\":{\"rate\":0.0},\"messages_unacknowledged\":0,\"messages_unacknowledged_details\":{\"rate\":0.0},\"idle_since\":\"2016-03-11 0:33:28\",\"consumer_utilisation\":null,\"policy\":null,\"exclusive_consumer_tag\":null,\"consumers\":2,\"recoverable_slaves\":null,\"state\":\"running\",\"messages_ram\":0,\"messages_ready_ram\":0,\"messages_unacknowledged_ram\":0,\"messages_persistent\":0,\"message_bytes\":0,\"message_bytes_ready\":0,\"message_bytes_unacknowledged\":0,\"message_bytes_ram\":0,\"message_bytes_persistent\":0,\"head_message_timestamp\":null,\"disk_reads\":0,\"disk_writes\":0,\"backing_queue_status\":{\"mode\":\"default\",\"q1\":0,\"q2\":0,\"delta\":[\"delta\",\"undefined\",0,\"undefined\"],\"q3\":0,\"q4\":0,\"len\":0,\"target_ram_count\":\"infinity\",\"next_seq_id\":0,\"avg_ingress_rate\":0.0,\"avg_egress_rate\":0.0,\"avg_ack_ingress_rate\":0.0,\"avg_ack_egress_rate\":0.0},\"name\":\"17\",\"vhost\":\"/\",\"durable\":true,\"auto_delete\":false,\"exclusive\":false,\"arguments\":{},\"node\":\"testnode\"},{\"memory\":21920,\"messages\":0,\"messages_details\":{\"rate\":0.0},\"messages_ready\":0,\"messages_ready_details\":{\"rate\":0.0},\"messages_unacknowledged\":0,\"messages_unacknowledged_details\":{\"rate\":0.0},\"idle_since\":\"2016-03-11 0:33:28\",\"consumer_utilisation\":null,\"policy\":null,\"exclusive_consumer_tag\":null,\"consumers\":0,\"recoverable_slaves\":null,\"state\":\"running\",\"messages_ram\":0,\"messages_ready_ram\":0,\"messages_unacknowledged_ram\":0,\"messages_persistent\":0,\"message_bytes\":0,\"message_bytes_ready\":0,\"message_bytes_unacknowledged\":0,\"message_bytes_ram\":0,\"message_bytes_persistent\":0,\"head_message_timestamp\":null,\"disk_reads\":0,\"disk_writes\":0,\"backing_queue_status\":{\"mode\":\"default\",\"q1\":0,\"q2\":0,\"delta\":[\"delta\",\"undefined\",0,\"undefined\"],\"q3\":0,\"q4\":0,\"len\":0,\"target_ram_count\":\"infinity\",\"next_seq_id\":0,\"avg_ingress_rate\":0.0,\"avg_egress_rate\":0.0,\"avg_ack_ingress_rate\":0.0,\"avg_ack_egress_rate\":0.0},\"name\":\"41\",\"vhost\":\"/\",\"durable\":true,\"auto_delete\":false,\"exclusive\":false,\"arguments\":{},\"node\":\"testnode\"},{\"memory\":21920,\"messages\":0,\"messages_details\":{\"rate\":0.0},\"messages_ready\":0,\"messages_ready_details\":{\"rate\":0.0},\"messages_unacknowledged\":0,\"messages_unacknowledged_details\":{\"rate\":0.0},\"idle_since\":\"2016-03-11 0:33:27\",\"consumer_utilisation\":null,\"policy\":null,\"exclusive_consumer_tag\":null,\"consumers\":0,\"recoverable_slaves\":null,\"state\":\"running\",\"messages_ram\":0,\"messages_ready_ram\":0,\"messages_unacknowledged_ram\":0,\"messages_persistent\":0,\"message_bytes\":0,\"message_bytes_ready\":0,\"message_bytes_unacknowledged\":0,\"message_bytes_ram\":0,\"message_bytes_persistent\":0,\"head_message_timestamp\":null,\"disk_reads\":0,\"disk_writes\":0,\"backing_queue_status\":{\"mode\":\"default\",\"q1\":0,\"q2\":0,\"delta\":[\"delta\",\"undefined\",0,\"undefined\"],\"q3\":0,\"q4\":0,\"len\":0,\"target_ram_count\":\"infinity\",\"next_seq_id\":0,\"avg_ingress_rate\":0.0,\"avg_egress_rate\":0.0,\"avg_ack_ingress_rate\":0.0,\"avg_ack_egress_rate\":0.0},\"name\":\"42\",\"vhost\":\"/\",\"durable\":true,\"auto_delete\":false,\"exclusive\":false,\"arguments\":{},\"node\":\"testnode\"},{\"memory\":21848,\"message_stats\":{\"ack\":1361,\"ack_details\":{\"rate\":0.0},\"deliver\":1361,\"deliver_details\":{\"rate\":0.0},\"deliver_get\":1361,\"deliver_get_details\":{\"rate\":0.0},\"publish\":1362,\"publish_details\":{\"rate\":0.0}},\"messages\":1,\"messages_details\":{\"rate\":0.0},\"messages_ready\":1,\"messages_ready_details\":{\"rate\":0.0},\"messages_unacknowledged\":0,\"messages_unacknowledged_details\":{\"rate\":0.0},\"idle_since\":\"2016-03-11 0:33:31\",\"consumer_utilisation\":null,\"policy\":null,\"exclusive_consumer_tag\":null,\"consumers\":0,\"recoverable_slaves\":null,\"state\":\"running\",\"messages_ram\":1,\"messages_ready_ram\":1,\"messages_unacknowledged_ram\":0,\"messages_persistent\":0,\"message_bytes\":191,\"message_bytes_ready\":191,\"message_bytes_unacknowledged\":0,\"message_bytes_ram\":191,\"message_bytes_persistent\":0,\"head_message_timestamp\":null,\"disk_reads\":0,\"disk_writes\":0,\"backing_queue_status\":{\"mode\":\"default\",\"q1\":0,\"q2\":0,\"delta\":[\"delta\",\"undefined\",0,\"undefined\"],\"q3\":0,\"q4\":1,\"len\":1,\"target_ram_count\":\"infinity\",\"next_seq_id\":1362,\"avg_ingress_rate\":0.0005576266319825132,\"avg_egress_rate\":6.819506093702665e-109,\"avg_ack_ingress_rate\":6.819506093702665e-109,\"avg_ack_egress_rate\":6.824693279456112e-109},\"name\":\"aa275b1eeb0f340a6430180c9a703795\",\"vhost\":\"/\",\"durable\":false,\"auto_delete\":false,\"exclusive\":false,\"arguments\":{},\"node\":\"testnode\"}]"; + + @Before + public void setUp() throws Exception { + pushSyncHelper = Mockito.mock(RabbitMQPushSyncHelper.class); + pushSyncHelper.template = Mockito.mock(AmqpTemplate.class); + pushSyncHelper.objectMapper = new ObjectMapper(); + pushSyncHelper.accessManager = Mockito.mock(IAccessManager.class); + pushSyncHelper.cacheDataStore = Mockito.mock(ICacheDataStore.class); + } + + @After + public void tearDown() throws Exception { + } + + @SuppressWarnings("unchecked") + @Test + public void testValidateQueues() { + + // Should not run when it is currently being run. + pushSyncHelper.validatingQueues = true; + boolean result = pushSyncHelper.validateQueues(); + assertFalse(result); + pushSyncHelper.validatingQueues = false; + + // Setup QueueProperties to check + final Set queuePropertiesSet = new HashSet(); + + QueueProperties queueProperties = new QueueProperties(); + queueProperties.setQueueName("QUEUE_1"); + queuePropertiesSet.add(queueProperties); + QueueProperties queueProperties2 = new QueueProperties(); + queueProperties2.setQueueName("QUEUE_2"); + queuePropertiesSet.add(queueProperties2); + + Mockito.when(pushSyncHelper.validateQueues()).thenCallRealMethod(); + try { + Mockito.when(pushSyncHelper.retrieveQueueProperties(Mockito.anyBoolean())) + .thenAnswer(new Answer>() { + @Override + public Set answer(InvocationOnMock invocation) throws Throwable { + return queuePropertiesSet; + } + }); + } catch (JsonSyntaxException | IOException e) { + fail(e.getMessage()); + } + + Set validClients = new HashSet(); + try { + Mockito.when(pushSyncHelper.accessManager.validateClients(Mockito.anyCollection())).thenReturn(validClients); + } catch (Exception e) { + fail(e.getMessage()); + } + + // If queue is a valid client, then do NOT log them out. + try { + Mockito.verify(pushSyncHelper.accessManager, Mockito.never()).logoutClient(Mockito.anyString(), Mockito.anyBoolean()); + } catch (Exception e) { + fail(e.getMessage()); + } + + validClients.add("QUEUE_1"); + validClients.add("QUEUE_2"); + Mockito.when(pushSyncHelper.checkQueueForDeletion(Mockito.any(QueueProperties.class))).thenReturn(true).thenReturn(false); + Mockito.when(pushSyncHelper.checkQueueForLogout(Mockito.any(QueueProperties.class))).thenReturn(true); + + result = pushSyncHelper.validateQueues(); + assertTrue(result); + // Make sure we finish cleanly. + assertFalse(pushSyncHelper.validatingQueues); + + Mockito.verify(pushSyncHelper, Mockito.times(2)).checkQueueForDeletion(Mockito.any(QueueProperties.class)); + Mockito.verify(pushSyncHelper, Mockito.times(1)).checkQueueForLogout(Mockito.any(QueueProperties.class)); + Mockito.verify(pushSyncHelper, Mockito.times(1)).deleteQueue(Mockito.anyString()); + try { + Mockito.verify(pushSyncHelper.accessManager, Mockito.never()).logoutClient(Mockito.anyString(), Mockito.anyBoolean()); + } catch (Exception e) { + fail(e.getMessage()); + } + + // If queue is NOT a valid client, then DO log them out. + validClients.clear(); + Mockito.when(pushSyncHelper.checkQueueForDeletion(Mockito.any(QueueProperties.class))).thenReturn(true).thenReturn(false); + Mockito.when(pushSyncHelper.checkQueueForLogout(Mockito.any(QueueProperties.class))).thenReturn(true); + result = pushSyncHelper.validateQueues(); + assertTrue(result); + // Make sure we finish cleanly. + assertFalse(pushSyncHelper.validatingQueues); + + Mockito.verify(pushSyncHelper, Mockito.times(4)).checkQueueForDeletion(Mockito.any(QueueProperties.class)); + Mockito.verify(pushSyncHelper, Mockito.times(2)).checkQueueForLogout(Mockito.any(QueueProperties.class)); + Mockito.verify(pushSyncHelper, Mockito.times(2)).deleteQueue(Mockito.anyString()); + try { + Mockito.verify(pushSyncHelper.accessManager, Mockito.times(1)).logoutClient(Mockito.anyString(), Mockito.anyBoolean()); + } catch (Exception e) { + fail(e.getMessage()); + } + } + + @Test + public void testCheckQueueForDeletion() { + String queueName = "QUEUE"; + int numMessages = 0; + int numConsumers = 0; + Instant dateTimeIdleSince = Instant.now().minus(Duration.standardDays(7).getMillis()); + boolean isEolClientsMember = false; + + Mockito.when(pushSyncHelper.checkQueueForDeletion(Mockito.anyString(), Mockito.anyInt(), Mockito.anyInt(), Mockito.any(Instant.class), Mockito.anyBoolean())).thenCallRealMethod(); + Mockito.when(pushSyncHelper.queueHasTimedOut(Mockito.anyString(), Mockito.any(Instant.class))).thenCallRealMethod(); + + // If queueName is in QueueNames, then false + pushSyncHelper.queueNames = new HashSet<>(); + pushSyncHelper.queueNames.add(queueName); + boolean result = pushSyncHelper.checkQueueForDeletion(queueName, numMessages, numConsumers, dateTimeIdleSince, isEolClientsMember); + assertFalse(result); + + // If no consumers and IS EOL client with no messages, then true + // if isEolClientsMember AND (numMessages <= 0 OR numConsumers == 0), then true + pushSyncHelper.queueNames = new HashSet<>(); + isEolClientsMember = true; + numConsumers = 0; + numMessages = 5; + result = pushSyncHelper.checkQueueForDeletion(queueName, numMessages, numConsumers, dateTimeIdleSince, isEolClientsMember); + assertTrue(result); + + numConsumers = 2; + numMessages = 0; + result = pushSyncHelper.checkQueueForDeletion(queueName, numMessages, numConsumers, dateTimeIdleSince, isEolClientsMember); + assertTrue(result); + + numConsumers = 0; + numMessages = 0; + result = pushSyncHelper.checkQueueForDeletion(queueName, numMessages, numConsumers, dateTimeIdleSince, isEolClientsMember); + assertTrue(result); + + // If NOT EOL Client Member AND Consumers, then false + isEolClientsMember = false; + numConsumers = 5; + result = pushSyncHelper.checkQueueForDeletion(queueName, numMessages, numConsumers, dateTimeIdleSince, isEolClientsMember); + assertFalse(result); + + // If numConsumers == 0 AND numMessages > 0 AND queue has NOT timed out, then false + isEolClientsMember = false; + numMessages = 12; + numConsumers = 0; + dateTimeIdleSince = Instant.now(); + pushSyncHelper.rabbitQueueTimeout = 50 * 1000; // 50 s + result = pushSyncHelper.checkQueueForDeletion(queueName, numMessages, numConsumers, dateTimeIdleSince, isEolClientsMember); + assertFalse(result); + + // If numConsumers == 0 AND numMessages > 0 AND queue HAS timed out AND queue contains NO EOL message, then false + isEolClientsMember = false; + numMessages = 12; + numConsumers = 0; + dateTimeIdleSince = Instant.now().minus(Duration.standardDays(7).getMillis()); + pushSyncHelper.rabbitQueueTimeout = 500; // 500 ms + result = pushSyncHelper.checkQueueForDeletion(queueName, numMessages, numConsumers, dateTimeIdleSince, isEolClientsMember); + assertFalse(result); + + // If numConsumers == 0 AND numMessages > 0 AND queue HAS timed out AND queue contains EOL message, then true + isEolClientsMember = false; + numMessages = 12; + numConsumers = 0; + Message eolMessage = new Message("{\"EOL\": true}".getBytes(), null); + Mockito.when(pushSyncHelper.template.receive(Mockito.anyString())).thenReturn(eolMessage); + result = pushSyncHelper.checkQueueForDeletion(queueName, numMessages, numConsumers, dateTimeIdleSince, isEolClientsMember); + assertTrue(result); + + // Valid messages should be requeued. + isEolClientsMember = false; + numMessages = 12; + numConsumers = 0; + Message notEolMessage = new Message("{\"OTHER\": true}".getBytes(), null); + Mockito.when(pushSyncHelper.template.receive(Mockito.anyString())).thenReturn(notEolMessage).thenReturn(null); + Mockito.verify(pushSyncHelper.template, Mockito.never()).send(Mockito.anyString(), Mockito.any(Message.class)); + result = pushSyncHelper.checkQueueForDeletion(queueName, numMessages, numConsumers, dateTimeIdleSince, isEolClientsMember); + Mockito.verify(pushSyncHelper.template, Mockito.atLeastOnce()).send(Mockito.anyString(), Mockito.any(Message.class)); + assertFalse(result); + } + + @Test + public void testCheckQueueForLogout() { + String queueName = "QUEUE"; + int numMessages = 0; + int numConsumers = 0; + Instant dateTimeIdleSince = Instant.now().minus(Duration.standardDays(7).getMillis()); + boolean isEolClientsMember = false; + + Mockito.when(pushSyncHelper.checkQueueForLogout(Mockito.anyString(), Mockito.anyInt(), Mockito.anyInt(), Mockito.any(Instant.class), Mockito.anyBoolean())).thenCallRealMethod(); + Mockito.when(pushSyncHelper.queueHasTimedOut(Mockito.anyString(), Mockito.any(Instant.class))).thenCallRealMethod(); + + // If queueName is in QueueNames, then false + pushSyncHelper.queueNames = new HashSet<>(); + pushSyncHelper.queueNames.add(queueName); + boolean result = pushSyncHelper.checkQueueForLogout(queueName, numMessages, numConsumers, dateTimeIdleSince, isEolClientsMember); + assertFalse(result); + + // If EOL Client Member, then false + pushSyncHelper.queueNames = new HashSet<>(); + isEolClientsMember = true; + result = pushSyncHelper.checkQueueForLogout(queueName, numMessages, numConsumers, dateTimeIdleSince, isEolClientsMember); + assertFalse(result); + + // If NOT EOL Client Member AND numConsumers > 0, then false + pushSyncHelper.queueNames = new HashSet<>(); + isEolClientsMember = false; + numConsumers = 5; + result = pushSyncHelper.checkQueueForLogout(queueName, numMessages, numConsumers, dateTimeIdleSince, isEolClientsMember); + assertFalse(result); + + // If NOT EOL Client Member AND numConsumers <= 0 AND dateTimeIdleSince IS NULL, then false + pushSyncHelper.queueNames = new HashSet<>(); + isEolClientsMember = false; + numConsumers = 0; + dateTimeIdleSince = null; + result = pushSyncHelper.checkQueueForLogout(queueName, numMessages, numConsumers, dateTimeIdleSince, isEolClientsMember); + assertFalse(result); + + // If NOT EOL Client Member AND numConsumers <= 0 AND time difference > rabbitQueueTimeout, then true + pushSyncHelper.queueNames = new HashSet<>(); + isEolClientsMember = false; + numConsumers = 0; + dateTimeIdleSince = Instant.now().minus(Duration.standardDays(7).getMillis()); + pushSyncHelper.rabbitQueueTimeout = 500; // 500 ms + result = pushSyncHelper.checkQueueForLogout(queueName, numMessages, numConsumers, dateTimeIdleSince, isEolClientsMember); + assertTrue(result); + + // If NOT EOL Client Member AND numConsumers <= 0 AND time difference < rabbitQueueTimeout, then false + pushSyncHelper.queueNames = new HashSet<>(); + isEolClientsMember = false; + numConsumers = 0; + dateTimeIdleSince = Instant.now(); + pushSyncHelper.rabbitQueueTimeout = 50 * 1000; // 50 s + result = pushSyncHelper.checkQueueForLogout(queueName, numMessages, numConsumers, dateTimeIdleSince, isEolClientsMember); + assertFalse(result); + } + + @Test + public void testQueueHasTimedOut() { + String queueName = "QUEUE"; + Instant dateTimeIdleSince = Instant.now().minus(Duration.standardDays(7).getMillis()); + + Mockito.when(pushSyncHelper.queueHasTimedOut(Mockito.anyString(), Mockito.any(Instant.class))).thenCallRealMethod(); + + // If queueName is in QueueNames, then false + pushSyncHelper.queueNames = new HashSet<>(); + pushSyncHelper.queueNames.add(queueName); + boolean result = pushSyncHelper.queueHasTimedOut(queueName, dateTimeIdleSince); + assertFalse(result); + + // If time difference > rabbitQueueTimeout, then true + pushSyncHelper.queueNames = new HashSet<>(); + dateTimeIdleSince = Instant.now().minus(Duration.standardDays(7).getMillis()); + pushSyncHelper.rabbitQueueTimeout = 500; // 500 ms + result = pushSyncHelper.queueHasTimedOut(queueName, dateTimeIdleSince); + assertTrue(result); + + // If time difference < rabbitQueueTimeout, then false + pushSyncHelper.queueNames = new HashSet<>(); + dateTimeIdleSince = Instant.now(); + pushSyncHelper.rabbitQueueTimeout = 50 * 1000; // 50 s + result = pushSyncHelper.queueHasTimedOut(queueName, dateTimeIdleSince); + assertFalse(result); + } + + @Test + public void testRetrieveQueuesJsonListAsString() { + + try { + Mockito.when(pushSyncHelper.retrieveQueuesJsonListAsString()).thenCallRealMethod(); + + // If NO rabbit host, then should return an empty string + pushSyncHelper.rabbitHost = null; + String result = pushSyncHelper.retrieveQueuesJsonListAsString(); + assertEquals("", result); + + pushSyncHelper.rabbitHost = ""; + result = pushSyncHelper.retrieveQueuesJsonListAsString(); + assertEquals("", result); + + pushSyncHelper.rabbitHost = "localhost"; + pushSyncHelper.rabbitAdminPort = 15672; + pushSyncHelper.rabbitLogin = "guest"; + pushSyncHelper.rabbitPassword = "guest"; + + final String uri = "http://" + pushSyncHelper.rabbitHost + ":" + pushSyncHelper.rabbitAdminPort + "/api/queues/"; + Mockito.when(pushSyncHelper.issueHttpCall(Mockito.anyString(), Mockito.any(AuthScope.class), Mockito.any(Credentials.class))).then(new Answer() { + @Override + public String answer(InvocationOnMock invocation) throws Throwable { + if (uri.equals(invocation.getArguments()[0])) { + return jsonMessages; + } + else { + return ""; + } + } + }); + + String jsonList = pushSyncHelper.retrieveQueuesJsonListAsString(); + + assertEquals(jsonMessages, jsonList); + } catch (IOException e) { + fail(e.getMessage()); + } + + } + + @Test + public void testRetrieveQueueProperties() { + // TODO: Get a valid Rabbit Message JSON response (by setting rabbitHost and port), then use that as seed data for this method. + pushSyncHelper.rabbitHost = "localhost"; + pushSyncHelper.rabbitAdminPort = 15672; + pushSyncHelper.rabbitLogin = "guest"; + pushSyncHelper.rabbitPassword = "guest"; + pushSyncHelper.queueNames = new HashSet(); + pushSyncHelper.queueNames.add("41"); + + Mockito.when(pushSyncHelper.cacheDataStore.getSetIsMember(Mockito.anyString(), Mockito.anyObject())).thenAnswer(new Answer() { + @Override + public Boolean answer(InvocationOnMock invocation) throws Throwable { + if("42".equals(invocation.getArguments()[1])) { + return true; + } + else { + return false; + } + } + }); + + try { + Mockito.when(pushSyncHelper.retrieveQueuesJsonListAsString()).thenReturn(jsonMessages); + Mockito.when(pushSyncHelper.retrieveQueueProperties(Mockito.anyBoolean())).thenCallRealMethod(); + Set queueProperties = pushSyncHelper.retrieveQueueProperties(false); + assertNotNull(queueProperties); + // There are 4 queues in the JSON, but we do NOT want the queue named "41" since that has been registered as a "system" queue. + assertEquals(3, queueProperties.size()); + + for(QueueProperties qp : queueProperties) { + if ("42".equals(qp.getQueueName())) { + assertEquals(0, qp.getNumConsumers()); + assertEquals(0, qp.getNumMessages()); + assertTrue(qp.isEolClientsMember()); + assertEquals(new Instant(1457656407000l), qp.getDateTimeIdleSince()); + } + else if ("17".equals(qp.getQueueName())) { + assertEquals(2, qp.getNumConsumers()); + assertEquals(0, qp.getNumMessages()); + assertFalse(qp.isEolClientsMember()); + assertEquals(new Instant(1457656408000l), qp.getDateTimeIdleSince()); + } + else if ("aa275b1eeb0f340a6430180c9a703795".equals(qp.getQueueName())) { + assertEquals(0, qp.getNumConsumers()); + assertEquals(1, qp.getNumMessages()); + assertFalse(qp.isEolClientsMember()); + assertEquals(new Instant(1457656411000l), qp.getDateTimeIdleSince()); + } + } + + } catch (IOException e) { + fail(e.getMessage()); + } + + } + +} diff --git a/src/test/java/com/percero/client/AStackClient.java b/src/test/java/com/percero/client/AStackClient.java index f9c39e1..2eb92bd 100644 --- a/src/test/java/com/percero/client/AStackClient.java +++ b/src/test/java/com/percero/client/AStackClient.java @@ -1,14 +1,10 @@ package com.percero.client; +import java.util.UUID; + import com.percero.agents.auth.vo.AuthenticationRequest; import com.percero.agents.auth.vo.AuthenticationResponse; import com.percero.agents.auth.vo.UserToken; -import org.springframework.amqp.core.Message; -import org.springframework.amqp.core.MessageListener; -import org.springframework.amqp.rabbit.connection.ConnectionFactory; -import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; - -import java.util.UUID; /** * Intended to maintain session with the server and listen for @@ -21,10 +17,8 @@ public class AStackClient { */ private AStackRPCService rpcService; private String clientId = UUID.randomUUID().toString(); - private ConnectionFactory amqpConnectionFactory; - public AStackClient(AStackRPCService rpcService, ConnectionFactory amqpConnectionFactory){ + public AStackClient(AStackRPCService rpcService){ this.rpcService = rpcService; - this.amqpConnectionFactory = amqpConnectionFactory; } /** @@ -40,35 +34,7 @@ public boolean authenticateAnonymously(){ AuthenticationResponse response = rpcService.authenticate(request); userToken = response.getResult(); boolean result = (userToken != null); - if(result){ - setupClientQueueAndStartListening(); - } return result; } - - /** - * Sets up the queue to listen for messages to the client (including responses to RPC) - */ - private SimpleMessageListenerContainer listenerContainer; - private void setupClientQueueAndStartListening(){ - listenerContainer = new SimpleMessageListenerContainer(amqpConnectionFactory); - listenerContainer.setQueueNames(clientId); - listenerContainer.setMessageListener(getMessageListener()); - listenerContainer.start(); - } - - private MessageListener messageListener; - private MessageListener getMessageListener(){ - if(messageListener == null) - messageListener = new MessageListener() { - @Override - public void onMessage(Message message) { - String key = message.getMessageProperties().getReceivedRoutingKey(); - System.out.println("Got Message from server: "+key); - } - }; - - return messageListener; - } } diff --git a/src/test/java/com/percero/client/AStackClientFactory.java b/src/test/java/com/percero/client/AStackClientFactory.java index 18f4006..2982c9f 100644 --- a/src/test/java/com/percero/client/AStackClientFactory.java +++ b/src/test/java/com/percero/client/AStackClientFactory.java @@ -1,6 +1,5 @@ package com.percero.client; -import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -13,10 +12,7 @@ public class AStackClientFactory { @Autowired AStackRPCService rpcService; - @Autowired - ConnectionFactory connectionFactory; - public AStackClient getClient(){ - return new AStackClient(rpcService, connectionFactory); + return new AStackClient(rpcService); } } diff --git a/src/test/java/com/percero/client/AStackClientTest.java b/src/test/java/com/percero/client/AStackClientTest.java index 517a89c..0adcd83 100644 --- a/src/test/java/com/percero/client/AStackClientTest.java +++ b/src/test/java/com/percero/client/AStackClientTest.java @@ -1,14 +1,16 @@ package com.percero.client; -import com.percero.agents.sync.jobs.UpdateTablePoller; -import com.percero.test.utils.CleanerUtil; +import static org.junit.Assert.assertTrue; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import static org.junit.Assert.*; + +import com.percero.agents.sync.jobs.UpdateTablePoller; +import com.percero.test.utils.CleanerUtil; /** * Created by jonnysamps on 9/14/15. @@ -31,7 +33,7 @@ public void before(){ poller.enabled = false; cleanerUtil.cleanAll(); } - + @Test public void authenticateAnonymously(){ AStackClient client = clientFactory.getClient(); diff --git a/src/test/java/com/percero/client/AStackRPCService.java b/src/test/java/com/percero/client/AStackRPCService.java index 0818695..0a5831e 100644 --- a/src/test/java/com/percero/client/AStackRPCService.java +++ b/src/test/java/com/percero/client/AStackRPCService.java @@ -1,11 +1,12 @@ package com.percero.client; -import com.percero.agents.auth.vo.AuthenticationRequest; -import com.percero.agents.auth.vo.AuthenticationResponse; -import org.springframework.amqp.core.AmqpTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import com.percero.agents.auth.services.AuthService2; +import com.percero.agents.auth.vo.AuthenticationRequest; +import com.percero.agents.auth.vo.AuthenticationResponse; + /** * Main interface for communicating to the backend through AMQP * Created by jonnysamps on 9/13/15. @@ -14,10 +15,11 @@ public class AStackRPCService { @Autowired - AmqpTemplate amqpTemplate; + AuthService2 authService2; public AuthenticationResponse authenticate(AuthenticationRequest request){ - return (AuthenticationResponse) amqpTemplate.convertSendAndReceive("authenticate", request); + AuthenticationResponse response = (AuthenticationResponse) authService2.authenticate(request); + return response; } } diff --git a/src/test/java/com/percero/example/Country.java b/src/test/java/com/percero/example/Country.java new file mode 100644 index 0000000..f001795 --- /dev/null +++ b/src/test/java/com/percero/example/Country.java @@ -0,0 +1,11 @@ +package com.percero.example; + +import javax.persistence.Entity; + +import com.percero.example.mo_super._Super_Country; + +@Entity(name="Country") +public class Country extends _Super_Country +{ + +} diff --git a/src/test/java/com/percero/example/CountryPermit.java b/src/test/java/com/percero/example/CountryPermit.java new file mode 100644 index 0000000..73375e5 --- /dev/null +++ b/src/test/java/com/percero/example/CountryPermit.java @@ -0,0 +1,11 @@ +package com.percero.example; + +import javax.persistence.Entity; + +import com.percero.example.mo_super._Super_CountryPermit; + +@Entity(name="CountryPermit") +public class CountryPermit extends _Super_CountryPermit +{ + +} diff --git a/src/test/java/com/percero/example/ExampleManifest.java b/src/test/java/com/percero/example/ExampleManifest.java index a9cf5b9..0dfd43a 100644 --- a/src/test/java/com/percero/example/ExampleManifest.java +++ b/src/test/java/com/percero/example/ExampleManifest.java @@ -22,6 +22,13 @@ public List getClassList() { classList.add(PersonRole.class); classList.add(Email.class); classList.add(Block.class); + + classList.add(Country.class); + classList.add(CountryPermit.class); + classList.add(PermitDocument.class); + classList.add(PermitLine.class); + classList.add(PostalAddress.class); + classList.add(ShippingAddress.class); } return classList; } @@ -35,6 +42,13 @@ public List getObjectList() { objectList.add(new PersonRole()); objectList.add(new Email()); objectList.add(new Block()); + + objectList.add(new Country()); + objectList.add(new CountryPermit()); + objectList.add(new PermitDocument()); + objectList.add(new PermitLine()); + objectList.add(new PostalAddress()); + objectList.add(new ShippingAddress()); } return objectList; } @@ -48,6 +62,13 @@ public Map getUuidMap() { uuidMap.put("2", PersonRole.class); uuidMap.put("3", Email.class); uuidMap.put("4", Block.class); + + uuidMap.put("5", Country.class); + uuidMap.put("6", CountryPermit.class); + uuidMap.put("7", PermitDocument.class); + uuidMap.put("8", PermitLine.class); + uuidMap.put("9", PostalAddress.class); + uuidMap.put("10", ShippingAddress.class); } return uuidMap; } diff --git a/src/test/java/com/percero/example/PermitDocument.java b/src/test/java/com/percero/example/PermitDocument.java new file mode 100644 index 0000000..0be9de5 --- /dev/null +++ b/src/test/java/com/percero/example/PermitDocument.java @@ -0,0 +1,11 @@ +package com.percero.example; + +import javax.persistence.Entity; + +import com.percero.example.mo_super._Super_PermitDocument; + +@Entity(name="PermitDocument") +public class PermitDocument extends _Super_PermitDocument +{ + +} diff --git a/src/test/java/com/percero/example/PermitLine.java b/src/test/java/com/percero/example/PermitLine.java new file mode 100644 index 0000000..58c0d0d --- /dev/null +++ b/src/test/java/com/percero/example/PermitLine.java @@ -0,0 +1,11 @@ +package com.percero.example; + +import javax.persistence.Entity; + +import com.percero.example.mo_super._Super_PermitLine; + +@Entity(name="PermitLine") +public class PermitLine extends _Super_PermitLine +{ + +} diff --git a/src/test/java/com/percero/example/Person.java b/src/test/java/com/percero/example/Person.java index 1dccf05..dd13d44 100644 --- a/src/test/java/com/percero/example/Person.java +++ b/src/test/java/com/percero/example/Person.java @@ -2,15 +2,21 @@ import com.google.gson.JsonObject; import com.percero.agents.auth.vo.IUserAnchor; +import com.percero.agents.sync.metadata.MappedClass; +import com.percero.agents.sync.metadata.MappedClass.MappedClassMethodPair; import com.percero.agents.sync.metadata.annotations.EntityInterface; import com.percero.agents.sync.metadata.annotations.Externalize; import com.percero.agents.sync.metadata.annotations.PropertyInterface; import com.percero.agents.sync.vo.BaseDataObject; +import com.percero.serial.BDODeserializer; +import com.percero.serial.BDOSerializer; import com.percero.serial.JsonUtils; import org.codehaus.jackson.JsonGenerationException; import org.codehaus.jackson.annotate.JsonProperty; import org.codehaus.jackson.map.JsonMappingException; import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.map.annotate.JsonDeserialize; +import org.codehaus.jackson.map.annotate.JsonSerialize; import javax.persistence.*; import java.io.IOException; @@ -134,6 +140,21 @@ public List getCircles() { public void setCircles(List value) { this.circles = value; } + + + + @JsonSerialize(using=BDOSerializer.class) + @JsonDeserialize(using=BDODeserializer.class) + @com.percero.agents.sync.metadata.annotations.Externalize + @OneToOne(fetch=FetchType.LAZY, mappedBy="person", cascade=javax.persistence.CascadeType.REMOVE) + private PersonDetail personDetail; + public PersonDetail getPersonDetail() { + return this.personDetail; + } + public void setPersonDetail(PersonDetail value) { + this.personDetail = value; + } + ////////////////////////////////////////////////////// // JSON @@ -265,6 +286,17 @@ public String retrieveJson(ObjectMapper objectMapper) { } objectJson += "]"; + // Target Relationships + objectJson += ",\"personDetail\":"; + if (getPersonDetail() == null) + objectJson += "null"; + else { + try { + objectJson += ((BaseDataObject) getPersonDetail()).toEmbeddedJson(); + } catch(Exception e) { + objectJson += "null"; + } + } return objectJson; @@ -281,9 +313,22 @@ protected void fromJson(JsonObject jsonObject) { setFirstName(JsonUtils.getJsonString(jsonObject, "firstName")); setLastName(com.percero.serial.JsonUtils.getJsonString(jsonObject, "lastName")); - this.personRoles = (List) JsonUtils.getJsonListPerceroObject(jsonObject, "personRoles"); + this.personRoles = (List) JsonUtils.getJsonListPerceroObject(jsonObject, "personRoles"); this.emails = (List) JsonUtils.getJsonListPerceroObject(jsonObject, "emails"); this.circles = (List) JsonUtils.getJsonListPerceroObject(jsonObject, "circles"); + + // Target Relationships + this.personDetail = JsonUtils.getJsonPerceroObject(jsonObject, "personDetail"); } + @Override + protected List getListSetters() { + List listSetters = super.getListSetters(); + + // Target Relationships + listSetters.add(MappedClass.getFieldSetters(CountryPermit.class, "personDetail")); + + return listSetters; + } + } diff --git a/src/test/java/com/percero/example/PersonDetail.java b/src/test/java/com/percero/example/PersonDetail.java new file mode 100644 index 0000000..f621545 --- /dev/null +++ b/src/test/java/com/percero/example/PersonDetail.java @@ -0,0 +1,144 @@ +package com.percero.example; + +import java.io.IOException; +import java.io.Serializable; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToOne; + +import org.codehaus.jackson.JsonGenerationException; +import org.codehaus.jackson.annotate.JsonProperty; +import org.codehaus.jackson.map.JsonMappingException; +import org.codehaus.jackson.map.ObjectMapper; + +import com.google.gson.JsonObject; +import com.percero.agents.auth.vo.IUserRole; +import com.percero.agents.sync.metadata.annotations.Externalize; +import com.percero.agents.sync.metadata.annotations.RelationshipInterface; +import com.percero.agents.sync.vo.BaseDataObject; +import com.percero.serial.JsonUtils; + +@Entity +public class PersonDetail extends BaseDataObject implements Serializable +{ + ////////////////////////////////////////////////////// + // VERSION + ////////////////////////////////////////////////////// + @Override + public String classVersion() { + return "0.0.0"; + } + + + ////////////////////////////////////////////////////// + // ID + ////////////////////////////////////////////////////// + @Id + @Externalize + @Column(unique=true,name="ID") + private String ID; + @JsonProperty(value="ID") + public String getID() { + return this.ID; + } + @JsonProperty(value="ID") + public void setID(String value) { + this.ID = value; + } + + ////////////////////////////////////////////////////// + // Properties + ////////////////////////////////////////////////////// + @Column + @Externalize + private String detail1; + public String getDetail1() { + return this.detail1; + } + public void setDetail1(String value) + { + this.detail1 = value; + } + + + ////////////////////////////////////////////////////// + // Source Relationships + ////////////////////////////////////////////////////// + @RelationshipInterface(entityInterfaceClass=IUserRole.class, sourceVarName="userAnchor") + @Externalize + @JoinColumn(name="person_ID") + @org.hibernate.annotations.ForeignKey(name="FK_User_user_TO_UserRole") + @OneToOne(fetch=FetchType.LAZY, optional=false) + private Person person; + public Person getPerson() { + return this.person; + } + public void setPerson(Person value) { + this.person = value; + } + + + ////////////////////////////////////////////////////// + // JSON + ////////////////////////////////////////////////////// + @Override + public String retrieveJson(ObjectMapper objectMapper) { + String objectJson = super.retrieveJson(objectMapper); + + // Properties + objectJson += ",\"detail1\":"; + if (getDetail1() == null) + objectJson += "null"; + else { + if (objectMapper == null) + objectMapper = new ObjectMapper(); + try { + objectJson += objectMapper.writeValueAsString(getDetail1()); + } catch (JsonGenerationException e) { + objectJson += "null"; + e.printStackTrace(); + } catch (JsonMappingException e) { + objectJson += "null"; + e.printStackTrace(); + } catch (IOException e) { + objectJson += "null"; + e.printStackTrace(); + } + } + + // Source Relationships + objectJson += ",\"person\":"; + if (getPerson() == null) + objectJson += "null"; + else { + try { + objectJson += getPerson().toEmbeddedJson(); + } catch(Exception e) { + objectJson += "null"; + } + } + objectJson += ""; + + // Target Relationships + + return objectJson; + } + + @Override + protected void fromJson(JsonObject jsonObject) { + super.fromJson(jsonObject); + + // Properties + setDetail1(JsonUtils.getJsonString(jsonObject, "detail1")); + + // Source Relationships + this.person = JsonUtils.getJsonPerceroObject(jsonObject, "person"); + + // Target Relationships + } +} + diff --git a/src/test/java/com/percero/example/PostalAddress.java b/src/test/java/com/percero/example/PostalAddress.java new file mode 100644 index 0000000..811a892 --- /dev/null +++ b/src/test/java/com/percero/example/PostalAddress.java @@ -0,0 +1,11 @@ +package com.percero.example; + +import javax.persistence.Entity; + +import com.percero.example.mo_super._Super_PostalAddress; + +@Entity(name="PostalAddress") +public class PostalAddress extends _Super_PostalAddress +{ + +} diff --git a/src/test/java/com/percero/example/ShippingAddress.java b/src/test/java/com/percero/example/ShippingAddress.java new file mode 100644 index 0000000..586b4a5 --- /dev/null +++ b/src/test/java/com/percero/example/ShippingAddress.java @@ -0,0 +1,11 @@ +package com.percero.example; + +import javax.persistence.Entity; + +import com.percero.example.mo_super._Super_ShippingAddress; + +@Entity(name="ShippingAddress") +public class ShippingAddress extends _Super_ShippingAddress +{ + +} diff --git a/src/test/java/com/percero/example/mo_super/_Super_Country.java b/src/test/java/com/percero/example/mo_super/_Super_Country.java new file mode 100644 index 0000000..b079929 --- /dev/null +++ b/src/test/java/com/percero/example/mo_super/_Super_Country.java @@ -0,0 +1,196 @@ +package com.percero.example.mo_super; + +import java.io.IOException; +import java.io.Serializable; +import java.util.List; + +import javax.persistence.Column; +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; + +import org.codehaus.jackson.JsonGenerationException; +import org.codehaus.jackson.annotate.JsonProperty; +import org.codehaus.jackson.map.JsonMappingException; +import org.codehaus.jackson.map.ObjectMapper; + +import com.google.gson.JsonObject; +import com.percero.agents.sync.metadata.MappedClass; +import com.percero.agents.sync.metadata.MappedClass.MappedClassMethodPair; +import com.percero.agents.sync.vo.BaseDataObject; +import com.percero.example.CountryPermit; +import com.percero.example.PostalAddress; +import com.percero.serial.JsonUtils; + +@MappedSuperclass +@NamedQueries({ + @NamedQuery(name="updateQuery", query="sql:SELECT COUNT(DISTINCT (p.ID)) FROM Person p INNER JOIN PersonRole pr ON p.ID=pr.person_ID WHERE p.userId=:userId AND pr.roleName='PSI Global Admin'"), + @NamedQuery(name="createQuery", query="sql:SELECT COUNT(DISTINCT (p.ID)) FROM Person p INNER JOIN PersonRole pr ON p.ID=pr.person_ID WHERE p.userId=:userId AND pr.roleName='PSI Global Admin'"), + @NamedQuery(name="deleteQuery", query="sql:SELECT COUNT(DISTINCT (p.ID)) FROM Person p INNER JOIN PersonRole pr ON p.ID=pr.person_ID WHERE p.userId=:userId AND pr.roleName='PSI Global Admin' AND (SELECT COUNT(ae.ID) FROM AccountingEntity ae WHERE ae.country_ID=:id) = 0 AND (SELECT COUNT(pa.ID) FROM PostalAddress pa WHERE pa.country_ID=:id) = 0 AND (SELECT COUNT(vp.ID) FROM VarietyPatent vp WHERE vp.country_ID=:id) = 0") +}) +/* +*/ +public class _Super_Country extends BaseDataObject implements Serializable +{ + ////////////////////////////////////////////////////// + // VERSION + ////////////////////////////////////////////////////// + @Override + public String classVersion() { + return "0.0.0"; + } + + + ////////////////////////////////////////////////////// + // ID + ////////////////////////////////////////////////////// + @Id + @com.percero.agents.sync.metadata.annotations.Externalize + @Column(unique=true,name="ID") + private String ID; + @JsonProperty(value="ID") + public String getID() { + return this.ID; + } + @JsonProperty(value="ID") + public void setID(String value) { + this.ID = value; + } + + ////////////////////////////////////////////////////// + // Properties + ////////////////////////////////////////////////////// + @Column + @com.percero.agents.sync.metadata.annotations.Externalize + private String abbreviation; + public String getAbbreviation() { + return this.abbreviation; + } + public void setAbbreviation(String value) + { + this.abbreviation = value; + } + + @Column + @com.percero.agents.sync.metadata.annotations.Externalize + private Boolean requiresPermit; + public Boolean getRequiresPermit() { + return this.requiresPermit; + } + public void setRequiresPermit(Boolean value) + { + this.requiresPermit = value; + } + + @Column + @com.percero.agents.sync.metadata.annotations.Externalize + private String name; + public String getName() { + return this.name; + } + public void setName(String value) + { + this.name = value; + } + + + ////////////////////////////////////////////////////// + // Source Relationships + ////////////////////////////////////////////////////// + + + ////////////////////////////////////////////////////// + // Target Relationships + ////////////////////////////////////////////////////// + + + + ////////////////////////////////////////////////////// + // JSON + ////////////////////////////////////////////////////// + @Override + public String retrieveJson(ObjectMapper objectMapper) { + StringBuilder objectJson = new StringBuilder(super.retrieveJson(objectMapper)); + + // Properties + objectJson.append(",\"abbreviation\":"); + if (getAbbreviation() == null) + objectJson.append("null"); + else { + if (objectMapper == null) + objectMapper = new ObjectMapper(); + try { + objectJson.append(objectMapper.writeValueAsString(getAbbreviation())); + } catch (JsonGenerationException e) { + objectJson.append("null"); + e.printStackTrace(); + } catch (JsonMappingException e) { + objectJson.append("null"); + e.printStackTrace(); + } catch (IOException e) { + objectJson.append("null"); + e.printStackTrace(); + } + } + + objectJson.append(",\"requiresPermit\":"); + if (getRequiresPermit() == null) + objectJson.append("null"); + else { + objectJson.append(getRequiresPermit()); + } + + objectJson.append(",\"name\":"); + if (getName() == null) + objectJson.append("null"); + else { + if (objectMapper == null) + objectMapper = new ObjectMapper(); + try { + objectJson.append(objectMapper.writeValueAsString(getName())); + } catch (JsonGenerationException e) { + objectJson.append("null"); + e.printStackTrace(); + } catch (JsonMappingException e) { + objectJson.append("null"); + e.printStackTrace(); + } catch (IOException e) { + objectJson.append("null"); + e.printStackTrace(); + } + } + + // Source Relationships + + // Target Relationships + + + return objectJson.toString(); + } + + @Override + protected void fromJson(JsonObject jsonObject) { + super.fromJson(jsonObject); + + // Properties + setAbbreviation(JsonUtils.getJsonString(jsonObject, "abbreviation")); + setRequiresPermit(JsonUtils.getJsonBoolean(jsonObject, "requiresPermit")); + setName(JsonUtils.getJsonString(jsonObject, "name")); + + // Source Relationships + + // Target Relationships + } + + @Override + protected List getListSetters() { + List listSetters = super.getListSetters(); + + // Target Relationships + listSetters.add(MappedClass.getFieldSetters(CountryPermit.class, "country")); + listSetters.add(MappedClass.getFieldSetters(PostalAddress.class, "country")); + + return listSetters; + } +} \ No newline at end of file diff --git a/src/test/java/com/percero/example/mo_super/_Super_CountryPermit.java b/src/test/java/com/percero/example/mo_super/_Super_CountryPermit.java new file mode 100644 index 0000000..4f4e0c1 --- /dev/null +++ b/src/test/java/com/percero/example/mo_super/_Super_CountryPermit.java @@ -0,0 +1,257 @@ +package com.percero.example.mo_super; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +import javax.persistence.Column; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; + +import org.codehaus.jackson.annotate.JsonProperty; +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.map.annotate.JsonDeserialize; +import org.codehaus.jackson.map.annotate.JsonSerialize; + +import com.google.gson.JsonObject; +import com.percero.agents.sync.metadata.MappedClass; +import com.percero.agents.sync.metadata.MappedClass.MappedClassMethodPair; +import com.percero.agents.sync.vo.BaseDataObject; +import com.percero.example.Country; +import com.percero.example.PermitDocument; +import com.percero.example.PermitLine; +import com.percero.example.PostalAddress; +import com.percero.serial.BDODeserializer; +import com.percero.serial.BDOSerializer; +import com.percero.serial.JsonUtils; + +@MappedSuperclass +/* +*/ +public class _Super_CountryPermit extends BaseDataObject implements Serializable +{ + ////////////////////////////////////////////////////// + // VERSION + ////////////////////////////////////////////////////// + @Override + public String classVersion() { + return "0.0.0"; + } + + + ////////////////////////////////////////////////////// + // ID + ////////////////////////////////////////////////////// + @Id + @com.percero.agents.sync.metadata.annotations.Externalize + @Column(unique=true,name="ID") + private String ID; + @JsonProperty(value="ID") + public String getID() { + return this.ID; + } + @JsonProperty(value="ID") + public void setID(String value) { + this.ID = value; + } + + ////////////////////////////////////////////////////// + // Properties + ////////////////////////////////////////////////////// + @Column + @com.percero.agents.sync.metadata.annotations.Externalize + private Double quantity; + public Double getQuantity() { + return this.quantity; + } + public void setQuantity(Double value) + { + this.quantity = value; + } + + @Column + @com.percero.agents.sync.metadata.annotations.Externalize + private Date expirationDate; + public Date getExpirationDate() { + return this.expirationDate; + } + public void setExpirationDate(Date value) + { + this.expirationDate = value; + } + + + ////////////////////////////////////////////////////// + // Source Relationships + ////////////////////////////////////////////////////// + @com.percero.agents.sync.metadata.annotations.Externalize + @JsonSerialize(using=BDOSerializer.class) + @JsonDeserialize(using=BDODeserializer.class) + @JoinColumn(name="country_ID") + @org.hibernate.annotations.ForeignKey(name="FK_Country_country_TO_CountryPermit") + @ManyToOne(fetch=FetchType.LAZY, optional=false) + private Country country; + public Country getCountry() { + return this.country; + } + public void setCountry(Country value) { + this.country = value; + } + + @com.percero.agents.sync.metadata.annotations.Externalize + @JsonSerialize(using=BDOSerializer.class) + @JsonDeserialize(using=BDODeserializer.class) + @JoinColumn(name="issuerAddress_ID") + @org.hibernate.annotations.ForeignKey(name="FK_PostalAddress_issuerAddress_TO_CountryPermit") + @OneToOne(fetch=FetchType.LAZY) + private PostalAddress issuerAddress; + public PostalAddress getIssuerAddress() { + return this.issuerAddress; + } + public void setIssuerAddress(PostalAddress value) { + this.issuerAddress = value; + } + + + ////////////////////////////////////////////////////// + // Target Relationships + ////////////////////////////////////////////////////// + @com.percero.agents.sync.metadata.annotations.Externalize + @JsonSerialize(contentUsing=BDOSerializer.class) + @JsonDeserialize(contentUsing=BDODeserializer.class) + @OneToMany(fetch=FetchType.LAZY, targetEntity=PermitLine.class, mappedBy="countryPermit", cascade=javax.persistence.CascadeType.REMOVE) + private List permitLines; + public List getPermitLines() { + return this.permitLines; + } + public void setPermitLines(List value) { + this.permitLines = value; + } + + @JsonSerialize(using=BDOSerializer.class) + @JsonDeserialize(using=BDODeserializer.class) + @com.percero.agents.sync.metadata.annotations.Externalize + @JoinColumn(name="permitDocument_ID") + @org.hibernate.annotations.ForeignKey(name="FK_CountryPermit_permitDoc_PermitDocument") + @OneToOne(fetch=FetchType.LAZY, mappedBy="countryPermit", cascade=javax.persistence.CascadeType.REMOVE) + private PermitDocument permitDocument; + public PermitDocument getPermitDocument() { + return this.permitDocument; + } + public void setPermitDocument(PermitDocument value) { + this.permitDocument = value; + } + + + + + ////////////////////////////////////////////////////// + // JSON + ////////////////////////////////////////////////////// + @Override + public String retrieveJson(ObjectMapper objectMapper) { + StringBuilder objectJson = new StringBuilder(super.retrieveJson(objectMapper)); + + // Properties + objectJson.append(",\"quantity\":"); + if (getQuantity() == null) + objectJson.append("null"); + else { + objectJson.append(getQuantity()); + } + + objectJson.append(",\"expirationDate\":"); + if (getExpirationDate() == null) + objectJson.append("null"); + else { + objectJson.append(getExpirationDate().getTime()); + } + + // Source Relationships + objectJson.append(",\"country\":"); + if (getCountry() == null) + objectJson.append("null"); + else { + try { + objectJson.append(((BaseDataObject) getCountry()).toEmbeddedJson()); + } catch(Exception e) { + objectJson.append("null"); + } + } + + objectJson.append(",\"issuerAddress\":"); + if (getIssuerAddress() == null) + objectJson.append("null"); + else { + try { + objectJson.append(((BaseDataObject) getIssuerAddress()).toEmbeddedJson()); + } catch(Exception e) { + objectJson.append("null"); + } + } + + // Target Relationships + objectJson.append(",\"permitLines\":["); + if (getPermitLines() != null) { + int permitLinesCounter = 0; + for(PermitLine nextPermitLines : getPermitLines()) { + if (permitLinesCounter > 0) + objectJson.append(','); + try { + objectJson.append(((BaseDataObject) nextPermitLines).toEmbeddedJson()); + permitLinesCounter++; + } catch(Exception e) { + // Do nothing. + } + } + } + objectJson.append(']'); + + objectJson.append(",\"permitDocument\":"); + if (getPermitDocument() == null) + objectJson.append("null"); + else { + try { + objectJson.append(((BaseDataObject) getPermitDocument()).toEmbeddedJson()); + } catch(Exception e) { + objectJson.append("null"); + } + } + + + return objectJson.toString(); + } + + @Override + protected void fromJson(JsonObject jsonObject) { + super.fromJson(jsonObject); + + // Properties + setQuantity(JsonUtils.getJsonDouble(jsonObject, "quantity")); + setExpirationDate(JsonUtils.getJsonDate(jsonObject, "expirationDate")); + + // Source Relationships + this.country = JsonUtils.getJsonPerceroObject(jsonObject, "country"); + this.issuerAddress = JsonUtils.getJsonPerceroObject(jsonObject, "issuerAddress"); + + // Target Relationships + this.permitLines = (List) JsonUtils.getJsonListPerceroObject(jsonObject, "permitLines"); + this.permitDocument = JsonUtils.getJsonPerceroObject(jsonObject, "permitDocument"); + } + + @Override + protected List getListSetters() { + List listSetters = super.getListSetters(); + + // Target Relationships + listSetters.add(MappedClass.getFieldSetters(PermitLine.class, "countryPermit")); + listSetters.add(MappedClass.getFieldSetters(PermitDocument.class, "countryPermit")); + + return listSetters; + } +} \ No newline at end of file diff --git a/src/test/java/com/percero/example/mo_super/_Super_PermitDocument.java b/src/test/java/com/percero/example/mo_super/_Super_PermitDocument.java new file mode 100644 index 0000000..c302597 --- /dev/null +++ b/src/test/java/com/percero/example/mo_super/_Super_PermitDocument.java @@ -0,0 +1,164 @@ +package com.percero.example.mo_super; + +import java.io.IOException; +import java.io.Serializable; +import java.util.List; + +import javax.persistence.Column; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.MappedSuperclass; +import javax.persistence.OneToOne; + +import org.codehaus.jackson.JsonGenerationException; +import org.codehaus.jackson.annotate.JsonProperty; +import org.codehaus.jackson.map.JsonMappingException; +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.map.annotate.JsonDeserialize; +import org.codehaus.jackson.map.annotate.JsonSerialize; + +import com.google.gson.JsonObject; +import com.percero.agents.sync.metadata.MappedClass.MappedClassMethodPair; +import com.percero.agents.sync.vo.BaseDataObject; +import com.percero.example.CountryPermit; +import com.percero.serial.BDODeserializer; +import com.percero.serial.BDOSerializer; +import com.percero.serial.JsonUtils; + +@MappedSuperclass +/* +*/ +public class _Super_PermitDocument extends BaseDataObject implements Serializable +{ + ////////////////////////////////////////////////////// + // VERSION + ////////////////////////////////////////////////////// + @Override + public String classVersion() { + return "0.0.0.0"; + } + + + ////////////////////////////////////////////////////// + // ID + ////////////////////////////////////////////////////// + @Id + @com.percero.agents.sync.metadata.annotations.Externalize + @Column(unique=true,name="ID") + private String ID; + @JsonProperty(value="ID") + public String getID() { + return this.ID; + } + @JsonProperty(value="ID") + public void setID(String value) { + this.ID = value; + } + + ////////////////////////////////////////////////////// + // Properties + ////////////////////////////////////////////////////// + @Column + @com.percero.agents.sync.metadata.annotations.Externalize + private byte[] documentContent; + public byte[] getDocumentContent() { + return this.documentContent; + } + public void setDocumentContent(byte[] value) + { + this.documentContent = value; + } + + + ////////////////////////////////////////////////////// + // Source Relationships + ////////////////////////////////////////////////////// + @com.percero.agents.sync.metadata.annotations.Externalize + @JsonSerialize(using=BDOSerializer.class) + @JsonDeserialize(using=BDODeserializer.class) + @JoinColumn(name="countryPermit_ID") + @org.hibernate.annotations.ForeignKey(name="FK_CountryPermit_countryPermit_TO_PermitDocument") + @OneToOne(fetch=FetchType.LAZY, optional=false) + private CountryPermit countryPermit; + public CountryPermit getCountryPermit() { + return this.countryPermit; + } + public void setCountryPermit(CountryPermit value) { + this.countryPermit = value; + } + + + ////////////////////////////////////////////////////// + // Target Relationships + ////////////////////////////////////////////////////// + + + + ////////////////////////////////////////////////////// + // JSON + ////////////////////////////////////////////////////// + @Override + public String retrieveJson(ObjectMapper objectMapper) { + StringBuilder objectJson = new StringBuilder(super.retrieveJson(objectMapper)); + + // Properties + objectJson.append(",\"documentContent\":"); + if (getDocumentContent() == null) + objectJson.append("null"); + else { + if (objectMapper == null) + objectMapper = new ObjectMapper(); + try { + objectJson.append(objectMapper.writeValueAsString(getDocumentContent())); + } catch (JsonGenerationException e) { + objectJson.append("null"); + e.printStackTrace(); + } catch (JsonMappingException e) { + objectJson.append("null"); + e.printStackTrace(); + } catch (IOException e) { + objectJson.append("null"); + e.printStackTrace(); + } + } + + // Source Relationships + objectJson.append(",\"countryPermit\":"); + if (getCountryPermit() == null) + objectJson.append("null"); + else { + try { + objectJson.append(((BaseDataObject) getCountryPermit()).toEmbeddedJson()); + } catch(Exception e) { + objectJson.append("null"); + } + } + + // Target Relationships + + return objectJson.toString(); + } + + @Override + protected void fromJson(JsonObject jsonObject) { + super.fromJson(jsonObject); + + // Properties + setDocumentContent(JsonUtils.getJsonByteArray(jsonObject, "documentContent")); + + // Source Relationships + this.countryPermit = JsonUtils.getJsonPerceroObject(jsonObject, "countryPermit"); + + // Target Relationships + } + + @Override + protected List getListSetters() { + List listSetters = super.getListSetters(); + + // Target Relationships + + return listSetters; + } +} \ No newline at end of file diff --git a/src/test/java/com/percero/example/mo_super/_Super_PermitLine.java b/src/test/java/com/percero/example/mo_super/_Super_PermitLine.java new file mode 100644 index 0000000..e9bc05c --- /dev/null +++ b/src/test/java/com/percero/example/mo_super/_Super_PermitLine.java @@ -0,0 +1,148 @@ +package com.percero.example.mo_super; + +import java.io.Serializable; +import java.util.List; + +import javax.persistence.Column; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; + +import org.codehaus.jackson.annotate.JsonProperty; +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.map.annotate.JsonDeserialize; +import org.codehaus.jackson.map.annotate.JsonSerialize; + +import com.google.gson.JsonObject; +import com.percero.agents.sync.metadata.MappedClass.MappedClassMethodPair; +import com.percero.agents.sync.vo.BaseDataObject; +import com.percero.example.CountryPermit; +import com.percero.serial.BDODeserializer; +import com.percero.serial.BDOSerializer; +import com.percero.serial.JsonUtils; + +@MappedSuperclass +/* +*/ +public class _Super_PermitLine extends BaseDataObject implements Serializable +{ + ////////////////////////////////////////////////////// + // VERSION + ////////////////////////////////////////////////////// + @Override + public String classVersion() { + return "0.0.0"; + } + + + ////////////////////////////////////////////////////// + // ID + ////////////////////////////////////////////////////// + @Id + @com.percero.agents.sync.metadata.annotations.Externalize + @Column(unique=true,name="ID") + private String ID; + @JsonProperty(value="ID") + public String getID() { + return this.ID; + } + @JsonProperty(value="ID") + public void setID(String value) { + this.ID = value; + } + + ////////////////////////////////////////////////////// + // Properties + ////////////////////////////////////////////////////// + @Column + @com.percero.agents.sync.metadata.annotations.Externalize + private Double quantity; + public Double getQuantity() { + return this.quantity; + } + public void setQuantity(Double value) + { + this.quantity = value; + } + + + ////////////////////////////////////////////////////// + // Source Relationships + ////////////////////////////////////////////////////// + @com.percero.agents.sync.metadata.annotations.Externalize + @JsonSerialize(using=BDOSerializer.class) + @JsonDeserialize(using=BDODeserializer.class) + @JoinColumn(name="countryPermit_ID") + @org.hibernate.annotations.ForeignKey(name="FK_CountryPermit_countryPermit_TO_PermitLine") + @ManyToOne(fetch=FetchType.LAZY, optional=false) + private CountryPermit countryPermit; + public CountryPermit getCountryPermit() { + return this.countryPermit; + } + public void setCountryPermit(CountryPermit value) { + this.countryPermit = value; + } + + + ////////////////////////////////////////////////////// + // Target Relationships + ////////////////////////////////////////////////////// + + + + ////////////////////////////////////////////////////// + // JSON + ////////////////////////////////////////////////////// + @Override + public String retrieveJson(ObjectMapper objectMapper) { + StringBuilder objectJson = new StringBuilder(super.retrieveJson(objectMapper)); + + // Properties + objectJson.append(",\"quantity\":"); + if (getQuantity() == null) + objectJson.append("null"); + else { + objectJson.append(getQuantity()); + } + + // Source Relationships + objectJson.append(",\"countryPermit\":"); + if (getCountryPermit() == null) + objectJson.append("null"); + else { + try { + objectJson.append(((BaseDataObject) getCountryPermit()).toEmbeddedJson()); + } catch(Exception e) { + objectJson.append("null"); + } + } + + // Target Relationships + + return objectJson.toString(); + } + + @Override + protected void fromJson(JsonObject jsonObject) { + super.fromJson(jsonObject); + + // Properties + setQuantity(JsonUtils.getJsonDouble(jsonObject, "quantity")); + + // Source Relationships + this.countryPermit = JsonUtils.getJsonPerceroObject(jsonObject, "countryPermit"); + + // Target Relationships + } + + @Override + protected List getListSetters() { + List listSetters = super.getListSetters(); + + // Target Relationships + + return listSetters; + } +} \ No newline at end of file diff --git a/src/test/java/com/percero/example/mo_super/_Super_PostalAddress.java b/src/test/java/com/percero/example/mo_super/_Super_PostalAddress.java new file mode 100644 index 0000000..5e6782f --- /dev/null +++ b/src/test/java/com/percero/example/mo_super/_Super_PostalAddress.java @@ -0,0 +1,495 @@ +package com.percero.example.mo_super; + +import java.io.IOException; +import java.io.Serializable; +import java.util.List; + +import javax.persistence.Column; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; +import javax.persistence.OneToOne; + +import org.codehaus.jackson.JsonGenerationException; +import org.codehaus.jackson.annotate.JsonProperty; +import org.codehaus.jackson.map.JsonMappingException; +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.map.annotate.JsonDeserialize; +import org.codehaus.jackson.map.annotate.JsonSerialize; + +import com.google.gson.JsonObject; +import com.percero.agents.sync.metadata.MappedClass; +import com.percero.agents.sync.metadata.MappedClass.MappedClassMethodPair; +import com.percero.agents.sync.vo.BaseDataObject; +import com.percero.example.Country; +import com.percero.example.CountryPermit; +import com.percero.serial.BDODeserializer; +import com.percero.serial.BDOSerializer; +import com.percero.serial.JsonUtils; + +@MappedSuperclass +//@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS) +@Inheritance(strategy=InheritanceType.JOINED) +/* +*/ +public class _Super_PostalAddress extends BaseDataObject implements Serializable +{ + ////////////////////////////////////////////////////// + // VERSION + ////////////////////////////////////////////////////// + @Override + public String classVersion() { + return "0.0.0"; + } + + + ////////////////////////////////////////////////////// + // ID + ////////////////////////////////////////////////////// + @Id + @com.percero.agents.sync.metadata.annotations.Externalize + @Column(unique=true,name="ID") + private String ID; + @JsonProperty(value="ID") + public String getID() { + return this.ID; + } + @JsonProperty(value="ID") + public void setID(String value) { + this.ID = value; + } + + ////////////////////////////////////////////////////// + // Properties + ////////////////////////////////////////////////////// + @Column + @com.percero.agents.sync.metadata.annotations.Externalize + private String address1; + public String getAddress1() { + return this.address1; + } + public void setAddress1(String value) + { + this.address1 = value; + } + + @Column + @com.percero.agents.sync.metadata.annotations.Externalize + private String address2; + public String getAddress2() { + return this.address2; + } + public void setAddress2(String value) + { + this.address2 = value; + } + + @Column + @com.percero.agents.sync.metadata.annotations.Externalize + private String address3; + public String getAddress3() { + return this.address3; + } + public void setAddress3(String value) + { + this.address3 = value; + } + + @Column + @com.percero.agents.sync.metadata.annotations.Externalize + private String city; + public String getCity() { + return this.city; + } + public void setCity(String value) + { + this.city = value; + } + + @Column + @com.percero.agents.sync.metadata.annotations.Externalize + private String state; + public String getState() { + return this.state; + } + public void setState(String value) + { + this.state = value; + } + + @Column + @com.percero.agents.sync.metadata.annotations.Externalize + private String postalCode; + public String getPostalCode() { + return this.postalCode; + } + public void setPostalCode(String value) + { + this.postalCode = value; + } + + @Column + @com.percero.agents.sync.metadata.annotations.Externalize + private String name; + public String getName() { + return this.name; + } + public void setName(String value) + { + this.name = value; + } + + @Column + @com.percero.agents.sync.metadata.annotations.Externalize + private Double latitude; + public Double getLatitude() { + return this.latitude; + } + public void setLatitude(Double value) + { + this.latitude = value; + } + + @Column + @com.percero.agents.sync.metadata.annotations.Externalize + private Double longitude; + public Double getLongitude() { + return this.longitude; + } + public void setLongitude(Double value) + { + this.longitude = value; + } + + @Column + @com.percero.agents.sync.metadata.annotations.Externalize + private String abbreviation; + public String getAbbreviation() { + return this.abbreviation; + } + public void setAbbreviation(String value) + { + this.abbreviation = value; + } + + @Column + @com.percero.agents.sync.metadata.annotations.Externalize + private String countyName; + public String getCountyName() { + return this.countyName; + } + public void setCountyName(String value) + { + this.countyName = value; + } + + + ////////////////////////////////////////////////////// + // Source Relationships + ////////////////////////////////////////////////////// + @com.percero.agents.sync.metadata.annotations.Externalize + @JsonSerialize(using=BDOSerializer.class) + @JsonDeserialize(using=BDODeserializer.class) + @JoinColumn(name="country_ID") + @org.hibernate.annotations.ForeignKey(name="FK_Country_country_TO_PostalAddress") + @ManyToOne(fetch=FetchType.LAZY) + private Country country; + public Country getCountry() { + return this.country; + } + public void setCountry(Country value) { + this.country = value; + } + + + ////////////////////////////////////////////////////// + // Target Relationships + ////////////////////////////////////////////////////// + /** + * The address of the entiy that issued the import permit. Used to determing the "Issuing County" for Packing Lists. + */ + @JsonSerialize(using=BDOSerializer.class) + @JsonDeserialize(using=BDODeserializer.class) + @com.percero.agents.sync.metadata.annotations.Externalize + @JoinColumn(name="countryPermit_ID") + @org.hibernate.annotations.ForeignKey(name="FK_PostalAddress_countryPermit_CountryPermit") + @OneToOne(fetch=FetchType.LAZY, mappedBy="issuerAddress", cascade=javax.persistence.CascadeType.REMOVE) + private CountryPermit countryPermit; + public CountryPermit getCountryPermit() { + return this.countryPermit; + } + public void setCountryPermit(CountryPermit value) { + this.countryPermit = value; + } + + + + + ////////////////////////////////////////////////////// + // JSON + ////////////////////////////////////////////////////// + @Override + public String retrieveJson(ObjectMapper objectMapper) { + StringBuilder objectJson = new StringBuilder(super.retrieveJson(objectMapper)); + + // Properties + objectJson.append(",\"address1\":"); + if (getAddress1() == null) + objectJson.append("null"); + else { + if (objectMapper == null) + objectMapper = new ObjectMapper(); + try { + objectJson.append(objectMapper.writeValueAsString(getAddress1())); + } catch (JsonGenerationException e) { + objectJson.append("null"); + e.printStackTrace(); + } catch (JsonMappingException e) { + objectJson.append("null"); + e.printStackTrace(); + } catch (IOException e) { + objectJson.append("null"); + e.printStackTrace(); + } + } + + objectJson.append(",\"address2\":"); + if (getAddress2() == null) + objectJson.append("null"); + else { + if (objectMapper == null) + objectMapper = new ObjectMapper(); + try { + objectJson.append(objectMapper.writeValueAsString(getAddress2())); + } catch (JsonGenerationException e) { + objectJson.append("null"); + e.printStackTrace(); + } catch (JsonMappingException e) { + objectJson.append("null"); + e.printStackTrace(); + } catch (IOException e) { + objectJson.append("null"); + e.printStackTrace(); + } + } + + objectJson.append(",\"address3\":"); + if (getAddress3() == null) + objectJson.append("null"); + else { + if (objectMapper == null) + objectMapper = new ObjectMapper(); + try { + objectJson.append(objectMapper.writeValueAsString(getAddress3())); + } catch (JsonGenerationException e) { + objectJson.append("null"); + e.printStackTrace(); + } catch (JsonMappingException e) { + objectJson.append("null"); + e.printStackTrace(); + } catch (IOException e) { + objectJson.append("null"); + e.printStackTrace(); + } + } + + objectJson.append(",\"city\":"); + if (getCity() == null) + objectJson.append("null"); + else { + if (objectMapper == null) + objectMapper = new ObjectMapper(); + try { + objectJson.append(objectMapper.writeValueAsString(getCity())); + } catch (JsonGenerationException e) { + objectJson.append("null"); + e.printStackTrace(); + } catch (JsonMappingException e) { + objectJson.append("null"); + e.printStackTrace(); + } catch (IOException e) { + objectJson.append("null"); + e.printStackTrace(); + } + } + + objectJson.append(",\"state\":"); + if (getState() == null) + objectJson.append("null"); + else { + if (objectMapper == null) + objectMapper = new ObjectMapper(); + try { + objectJson.append(objectMapper.writeValueAsString(getState())); + } catch (JsonGenerationException e) { + objectJson.append("null"); + e.printStackTrace(); + } catch (JsonMappingException e) { + objectJson.append("null"); + e.printStackTrace(); + } catch (IOException e) { + objectJson.append("null"); + e.printStackTrace(); + } + } + + objectJson.append(",\"postalCode\":"); + if (getPostalCode() == null) + objectJson.append("null"); + else { + if (objectMapper == null) + objectMapper = new ObjectMapper(); + try { + objectJson.append(objectMapper.writeValueAsString(getPostalCode())); + } catch (JsonGenerationException e) { + objectJson.append("null"); + e.printStackTrace(); + } catch (JsonMappingException e) { + objectJson.append("null"); + e.printStackTrace(); + } catch (IOException e) { + objectJson.append("null"); + e.printStackTrace(); + } + } + + objectJson.append(",\"name\":"); + if (getName() == null) + objectJson.append("null"); + else { + if (objectMapper == null) + objectMapper = new ObjectMapper(); + try { + objectJson.append(objectMapper.writeValueAsString(getName())); + } catch (JsonGenerationException e) { + objectJson.append("null"); + e.printStackTrace(); + } catch (JsonMappingException e) { + objectJson.append("null"); + e.printStackTrace(); + } catch (IOException e) { + objectJson.append("null"); + e.printStackTrace(); + } + } + + objectJson.append(",\"latitude\":"); + if (getLatitude() == null) + objectJson.append("null"); + else { + objectJson.append(getLatitude()); + } + + objectJson.append(",\"longitude\":"); + if (getLongitude() == null) + objectJson.append("null"); + else { + objectJson.append(getLongitude()); + } + + objectJson.append(",\"abbreviation\":"); + if (getAbbreviation() == null) + objectJson.append("null"); + else { + if (objectMapper == null) + objectMapper = new ObjectMapper(); + try { + objectJson.append(objectMapper.writeValueAsString(getAbbreviation())); + } catch (JsonGenerationException e) { + objectJson.append("null"); + e.printStackTrace(); + } catch (JsonMappingException e) { + objectJson.append("null"); + e.printStackTrace(); + } catch (IOException e) { + objectJson.append("null"); + e.printStackTrace(); + } + } + + objectJson.append(",\"countyName\":"); + if (getCountyName() == null) + objectJson.append("null"); + else { + if (objectMapper == null) + objectMapper = new ObjectMapper(); + try { + objectJson.append(objectMapper.writeValueAsString(getCountyName())); + } catch (JsonGenerationException e) { + objectJson.append("null"); + e.printStackTrace(); + } catch (JsonMappingException e) { + objectJson.append("null"); + e.printStackTrace(); + } catch (IOException e) { + objectJson.append("null"); + e.printStackTrace(); + } + } + + // Source Relationships + objectJson.append(",\"country\":"); + if (getCountry() == null) + objectJson.append("null"); + else { + try { + objectJson.append(((BaseDataObject) getCountry()).toEmbeddedJson()); + } catch(Exception e) { + objectJson.append("null"); + } + } + + // Target Relationships + objectJson.append(",\"countryPermit\":"); + if (getCountryPermit() == null) + objectJson.append("null"); + else { + try { + objectJson.append(((BaseDataObject) getCountryPermit()).toEmbeddedJson()); + } catch(Exception e) { + objectJson.append("null"); + } + } + + + return objectJson.toString(); + } + + @Override + protected void fromJson(JsonObject jsonObject) { + super.fromJson(jsonObject); + + // Properties + setAddress1(JsonUtils.getJsonString(jsonObject, "address1")); + setAddress2(JsonUtils.getJsonString(jsonObject, "address2")); + setAddress3(JsonUtils.getJsonString(jsonObject, "address3")); + setCity(JsonUtils.getJsonString(jsonObject, "city")); + setState(JsonUtils.getJsonString(jsonObject, "state")); + setPostalCode(JsonUtils.getJsonString(jsonObject, "postalCode")); + setName(JsonUtils.getJsonString(jsonObject, "name")); + setLatitude(JsonUtils.getJsonDouble(jsonObject, "latitude")); + setLongitude(JsonUtils.getJsonDouble(jsonObject, "longitude")); + setAbbreviation(JsonUtils.getJsonString(jsonObject, "abbreviation")); + setCountyName(JsonUtils.getJsonString(jsonObject, "countyName")); + + // Source Relationships + this.country = JsonUtils.getJsonPerceroObject(jsonObject, "country"); + + // Target Relationships + this.countryPermit = JsonUtils.getJsonPerceroObject(jsonObject, "countryPermit"); + } + + @Override + protected List getListSetters() { + List listSetters = super.getListSetters(); + + // Target Relationships + listSetters.add(MappedClass.getFieldSetters(CountryPermit.class, "issuerAddress")); + + return listSetters; + } +} \ No newline at end of file diff --git a/src/test/java/com/percero/example/mo_super/_Super_ShippingAddress.java b/src/test/java/com/percero/example/mo_super/_Super_ShippingAddress.java new file mode 100644 index 0000000..33362d6 --- /dev/null +++ b/src/test/java/com/percero/example/mo_super/_Super_ShippingAddress.java @@ -0,0 +1,78 @@ +package com.percero.example.mo_super; + +import java.util.List; + +import javax.persistence.MappedSuperclass; +import javax.persistence.SecondaryTable; + +import org.codehaus.jackson.map.ObjectMapper; + +import com.google.gson.JsonObject; +import com.percero.agents.sync.metadata.MappedClass.MappedClassMethodPair; + +@MappedSuperclass +@SecondaryTable(name="ShippingAddress") +/* +*/ +public class _Super_ShippingAddress extends com.percero.example.PostalAddress +{ + ////////////////////////////////////////////////////// + // VERSION + ////////////////////////////////////////////////////// + @Override + public String classVersion() { + return "0.0.0"; + } + + + ////////////////////////////////////////////////////// + // ID + ////////////////////////////////////////////////////// + /** Inherits from another Model Object Class, so no ID here. **/ + + ////////////////////////////////////////////////////// + // Properties + ////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////// + // Source Relationships + ////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////// + // Target Relationships + ////////////////////////////////////////////////////// + + + + ////////////////////////////////////////////////////// + // JSON + ////////////////////////////////////////////////////// + @Override + public String retrieveJson(ObjectMapper objectMapper) { + StringBuilder objectJson = new StringBuilder(super.retrieveJson(objectMapper)); + + // Properties + // Source Relationships + // Target Relationships + + return objectJson.toString(); + } + + @Override + protected void fromJson(JsonObject jsonObject) { + super.fromJson(jsonObject); + + // Properties + + // Source Relationships + + // Target Relationships + } + + @Override + protected List getListSetters() { + List listSetters = super.getListSetters(); + + return listSetters; + } +} \ No newline at end of file diff --git a/src/test/java/com/percero/util/TestMappedClassUtils.java b/src/test/java/com/percero/util/TestMappedClassUtils.java new file mode 100644 index 0000000..3438674 --- /dev/null +++ b/src/test/java/com/percero/util/TestMappedClassUtils.java @@ -0,0 +1,62 @@ +package com.percero.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.List; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.percero.example.PostalAddress; + +public class TestMappedClassUtils { + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + } + + @Before + public void setUp() throws Exception { + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void testGetClassFields() { + List result = MappedClassUtils.getClassFields(PostalAddress.class); + + assertNotNull(result); + assertEquals(17, result.size()); + } + + @Test + public void testGetFieldGetters() throws NoSuchFieldException, SecurityException { + List fields = MappedClassUtils.getClassFields(PostalAddress.class); + for(Field field : fields) { + Method result = MappedClassUtils.getFieldGetters(PostalAddress.class, field); + assertNotNull(result); + } + } + + @Test + public void testGetFieldSetters() throws NoSuchFieldException, SecurityException { + List fields = MappedClassUtils.getClassFields(PostalAddress.class); + for(Field field : fields) { + Method result = MappedClassUtils.getFieldSetters(PostalAddress.class, field); + assertNotNull(result); + } + } + +} diff --git a/src/test/resources/properties/test/env.properties b/src/test/resources/properties/test/env.properties index fcc8219..abd7b52 100644 --- a/src/test/resources/properties/test/env.properties +++ b/src/test/resources/properties/test/env.properties @@ -9,6 +9,7 @@ storeHistory=false # Application Database Details databaseProject.driverClassName=com.mysql.jdbc.Driver +databaseProject.jdbcUrl=jdbc:mysql://localhost:3306/as_Example?autoReconnect=true databaseProject.host=localhost databaseProject.port=3306 databaseProject.dbname=as_example @@ -17,6 +18,7 @@ databaseProject.password=root # AuthAgent Database Details databaseAuth.driverClassName=com.mysql.jdbc.Driver +databaseAuth.jdbcUrl=jdbc:mysql://localhost:3306/as_Example?autoReconnect=true databaseAuth.host=localhost databaseAuth.port=3306 databaseAuth.dbname=as_example diff --git a/src/test/resources/spring/basic_components_spring_config.xml b/src/test/resources/spring/basic_components_spring_config.xml new file mode 100644 index 0000000..23cd080 --- /dev/null +++ b/src/test/resources/spring/basic_components_spring_config.xml @@ -0,0 +1,348 @@ + + + + + + + + + + + + classpath:properties/test/env.properties + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + hibernate.dialect=org.hibernate.dialect.MySQLDialect + + hibernate.hbm2ddl.auto=$pf{databaseAuth.hibernate.hbm2ddl.auto:update} + + hibernate.show_sql=false + hibernate.format_sql=false + + hibernate.connection.aggressive_release=true + hibernate.jdbc.batch_size=20 + hibernate.connection.autocommit=false + hibernate.enable_lazy_load_no_trans=true + + + + + com.percero.amqp + com.percero.agents.auth.vo + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/spring/percero-spring-config.xml b/src/test/resources/spring/percero-spring-config.xml new file mode 100644 index 0000000..38c7038 --- /dev/null +++ b/src/test/resources/spring/percero-spring-config.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/spring/update_table_processor.xml b/src/test/resources/spring/update_table_processor.xml index 9d0ba64..4c1570b 100644 --- a/src/test/resources/spring/update_table_processor.xml +++ b/src/test/resources/spring/update_table_processor.xml @@ -34,6 +34,7 @@ + @@ -154,7 +155,7 @@ - + @@ -174,7 +175,7 @@ destroy-method="close" id="dataSource1"> + value="$pf{databaseAuth.jdbcUrl}" /> @@ -276,7 +277,7 @@ destroy-method="close" id="dataSourceProject"> + value="$pf{databaseProject.jdbcUrl}" /> diff --git a/src/test/resources/updateTableMap.yml b/src/test/resources/updateTableMap.yml new file mode 100644 index 0000000..c56ef1a --- /dev/null +++ b/src/test/resources/updateTableMap.yml @@ -0,0 +1,8 @@ +tableName: Email +classNames: + - com.percero.example.Email +--- +tableName: Person +classNames: + - com.percero.example.Person +---