Added Notification Module

Added Notification Module

Added Notification Module

Added Notification Module

Added license file for jar analyzer

Added entry into LICENSE_RELEASE file

Added gradle wrapper
This commit is contained in:
Adhyan
2017-08-20 21:21:24 +05:30
parent 455ef98276
commit c60c6601c0
51 changed files with 2810 additions and 24 deletions
+4
View File
@@ -300,4 +300,8 @@ This product bundles Backport Util Concurrent v3.1 written by Dawid Kurzyniec,
which is avilable under Public Domain license.
For details see licenses/binary/Backport.PL
This product bundles JarAnalyzer v1.2 written by Kirk Knoernschild,
which is available under BSD license.
For details see licences/binary/JarAnalyzer.BSD
******************************************
+1
View File
@@ -7,5 +7,6 @@ repos
.settings
.gradle
*.log
.idea
!gradle/wrapper/gradle-wrapper.jar
/gradle
+6 -1
View File
@@ -91,11 +91,16 @@ dependencies {
[group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.3.5'],
// Once we've switched to Java 8 this dep can be removed.
//[group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.0']
[group: 'org.springframework', name:'spring-jms'],
[group: 'org.apache.activemq', name: 'activemq-broker']
)
testCompile 'junit:junit:4.11',
'junit:junit-dep:4.11',
'org.mockito:mockito-core:1.9.5',
'com.jayway.restassured:rest-assured:2.3.3',
[group: 'org.springframework.boot', name: 'spring-boot-starter-test', version: springBootVersion]
[group: 'org.springframework.boot', name: 'spring-boot-starter-test', version: springBootVersion],
[group: 'com.mockrunner', name: 'mockrunner-jms', version: '1.0.6'],
[group: 'com.mockrunner', name: 'mockrunner-jdbc', version: '1.0.6']
}
+5 -1
View File
@@ -90,11 +90,15 @@ dependencies {
[group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.3.5'],
// Once we've switched to Java 8 this dep can be removed.
//[group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.0']
[group: 'org.springframework', name:'spring-jms'],
[group: 'org.apache.activemq', name: 'activemq-broker']
)
testCompile 'junit:junit:4.11',
'junit:junit-dep:4.11',
'org.mockito:mockito-core:1.9.5',
'com.jayway.restassured:rest-assured:2.3.3',
[group: 'org.springframework.boot', name: 'spring-boot-starter-test', version: springBootVersion]
[group: 'org.springframework.boot', name: 'spring-boot-starter-test', version: springBootVersion],
[group: 'com.mockrunner', name: 'mockrunner-jms', version: '1.0.6'],
[group: 'com.mockrunner', name: 'mockrunner-jdbc', version: '1.0.6']
}
@@ -0,0 +1,57 @@
package org.apache.fineract.integrationtests;
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import com.jayway.restassured.builder.RequestSpecBuilder;
import com.jayway.restassured.builder.ResponseSpecBuilder;
import com.jayway.restassured.http.ContentType;
import com.jayway.restassured.specification.RequestSpecification;
import com.jayway.restassured.specification.ResponseSpecification;
import org.apache.fineract.integrationtests.common.NotificationHelper;
import org.apache.fineract.integrationtests.common.Utils;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import java.util.HashMap;
public class NotificationApiTest {
private ResponseSpecification responseSpec;
private RequestSpecification requestSpec;
@Before
public void setUp() {
Utils.initializeRESTAssured();
this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
}
@Test
@SuppressWarnings("unchecked")
public void testNotificationRetrieval() {
HashMap<String, Object> response = (HashMap<String, Object>) NotificationHelper.getNotifications(this.requestSpec,
this.responseSpec, "");
System.out.println("Response : " + response.toString());
Assert.assertNotNull(response);
}
}
@@ -0,0 +1,43 @@
package org.apache.fineract.integrationtests.common;
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import com.jayway.restassured.specification.RequestSpecification;
import com.jayway.restassured.specification.ResponseSpecification;
public class NotificationHelper {
private final RequestSpecification requestSpec;
private final ResponseSpecification responseSpec;
private static final String NOTIFICATION_API_URL = "/fineract-provider/api/v1/notifications?" + Utils.TENANT_IDENTIFIER;
public NotificationHelper(RequestSpecification requestSpec, ResponseSpecification responseSpec) {
this.requestSpec = requestSpec;
this.responseSpec = responseSpec;
}
public static Object getNotifications(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final String jsonReturn) {
final String GET_NOTIFICATIONS_URL = NOTIFICATION_API_URL;
System.out.println("-----------------------------GET NOTIFICATIONS-----------------------------------");
return Utils.performServerGet(requestSpec, responseSpec, GET_NOTIFICATIONS_URL, "");
}
}
@@ -37,6 +37,7 @@ import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.infrastructure.security.data.PlatformRequestLog;
import org.apache.fineract.infrastructure.security.exception.InvalidTenantIdentiferException;
import org.apache.fineract.infrastructure.security.service.BasicAuthTenantDetailsService;
import org.apache.fineract.notification.service.NotificationReadPlatformService;
import org.apache.fineract.useradministration.domain.AppUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -77,7 +78,7 @@ public class TenantAwareBasicAuthenticationFilter extends BasicAuthenticationFil
private final ToApiJsonSerializer<PlatformRequestLog> toApiJsonSerializer;
private final ConfigurationDomainService configurationDomainService;
private final CacheWritePlatformService cacheWritePlatformService;
private final NotificationReadPlatformService notificationReadPlatformService;
private final String tenantRequestHeader = "Fineract-Platform-TenantId";
private final boolean exceptionIfHeaderMissing = true;
@@ -85,12 +86,14 @@ public class TenantAwareBasicAuthenticationFilter extends BasicAuthenticationFil
public TenantAwareBasicAuthenticationFilter(final AuthenticationManager authenticationManager,
final AuthenticationEntryPoint authenticationEntryPoint, final BasicAuthTenantDetailsService basicAuthTenantDetailsService,
final ToApiJsonSerializer<PlatformRequestLog> toApiJsonSerializer, final ConfigurationDomainService configurationDomainService,
final CacheWritePlatformService cacheWritePlatformService) {
final CacheWritePlatformService cacheWritePlatformService,
final NotificationReadPlatformService notificationReadPlatformService) {
super(authenticationManager, authenticationEntryPoint);
this.basicAuthTenantDetailsService = basicAuthTenantDetailsService;
this.toApiJsonSerializer = toApiJsonSerializer;
this.configurationDomainService = configurationDomainService;
this.cacheWritePlatformService = cacheWritePlatformService;
this.notificationReadPlatformService = notificationReadPlatformService;
}
@Override
@@ -167,6 +170,12 @@ public class TenantAwareBasicAuthenticationFilter extends BasicAuthenticationFil
throws IOException {
super.onSuccessfulAuthentication(request, response, authResult);
AppUser user = (AppUser) authResult.getPrincipal();
if (notificationReadPlatformService.hasUnreadNotifications(user.getId())) {
response.addHeader("X-Notification-Refresh", "true");
} else {
response.addHeader("X-Notification-Refresh", "false");
}
String pathURL = request.getRequestURI();
boolean isSelfServiceRequest = (pathURL != null && pathURL.contains("/self/"));
@@ -0,0 +1,89 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.notification.api;
import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
import org.apache.fineract.infrastructure.core.service.Page;
import org.apache.fineract.infrastructure.core.service.SearchParameters;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.notification.data.NotificationData;
import org.apache.fineract.notification.service.NotificationReadPlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
@Path("/notifications")
@Component
@Scope("singleton")
public class NotificationApiResource {
private final PlatformSecurityContext context;
private final NotificationReadPlatformService notificationReadPlatformService;
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final ToApiJsonSerializer<NotificationData> toApiJsonSerializer;
@Autowired
public NotificationApiResource(PlatformSecurityContext context,
NotificationReadPlatformService notificationReadPlatformService,
ApiRequestParameterHelper apiRequestParameterHelper,
ToApiJsonSerializer<NotificationData> toApiJsonSerializer) {
this.context = context;
this.notificationReadPlatformService = notificationReadPlatformService;
this.apiRequestParameterHelper = apiRequestParameterHelper;
this.toApiJsonSerializer = toApiJsonSerializer;
}
@GET
@Consumes({MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_JSON})
public String getAllNotifications(@Context final UriInfo uriInfo, @QueryParam("orderBy") final String orderBy,
@QueryParam("limit") final Integer limit,
@QueryParam("offset") final Integer offset,
@QueryParam("sortOrder") final String sortOrder,
@QueryParam("isRead") final boolean isRead) {
this.context.authenticatedUser();
final Page<NotificationData> notificationData;
final SearchParameters searchParameters = SearchParameters.forPagination(offset, limit, orderBy, sortOrder);
if (!isRead) {
notificationData = this.notificationReadPlatformService.getAllUnreadNotifications(searchParameters);
} else {
notificationData = this.notificationReadPlatformService.getAllNotifications(searchParameters);
}
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper
.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, notificationData);
}
@PUT
@Consumes({MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_JSON})
public void update() {
this.context.authenticatedUser();
this.notificationReadPlatformService.updateNotificationReadStatus();
}
}
@@ -0,0 +1,50 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.notification.cache;
public class CacheNotificationResponseHeader {
private boolean hasNotifications;
private Long lastFetch;
public CacheNotificationResponseHeader() {
}
public CacheNotificationResponseHeader(boolean hasNotifications, Long lastFetch) {
this.hasNotifications = hasNotifications;
this.lastFetch = lastFetch;
}
public boolean hasNotifications() {
return hasNotifications;
}
public void setHasNotifications(boolean hasNotifications) {
this.hasNotifications = hasNotifications;
}
public Long getLastFetch() {
return lastFetch;
}
public void setLastFetch(Long lastFetch) {
this.lastFetch = lastFetch;
}
}
@@ -0,0 +1,143 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.notification.data;
import java.io.Serializable;
import java.util.List;
public class NotificationData implements Serializable {
private Long id;
private String objectType;
private Long objectId;
private String action;
private Long actorId;
private String content;
private boolean isSystemGenerated;
private String tenantIdentifier;
private String createdAt;
private Long officeId;
private List<Long> userIds;
public NotificationData() {
}
public NotificationData(String objectType, Long objectId, String action, Long actorId, String content, boolean isSystemGenerated,
String tenantIdentifier, Long officeId, List<Long> userIds) {
this.objectType = objectType;
this.objectId = objectId;
this.action = action;
this.actorId = actorId;
this.content = content;
this.isSystemGenerated = isSystemGenerated;
this.tenantIdentifier = tenantIdentifier;
this.officeId = officeId;
this.userIds = userIds;
}
public Long getOfficeId() {
return officeId;
}
public void setOfficeId(Long officeId) {
this.officeId = officeId;
}
public List<Long> getUserIds() {
return userIds;
}
public void setUserId(List<Long> userIds) {
this.userIds = userIds;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getCreatedAt() {
return createdAt;
}
public void setCreatedAt(String createdAt) {
this.createdAt = createdAt;
}
public String getObjectType() {
return objectType;
}
public void setObjectType(String objectType) {
this.objectType = objectType;
}
public Long getObjectIdentfier() {
return objectId;
}
public void entifier(Long objectIdentifier) {
this.objectId = objectIdentifier;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public Long getActor() {
return actorId;
}
public void setActor(Long actorId) {
this.actorId = actorId;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public boolean isSystemGenerated() {
return isSystemGenerated;
}
public void setSystemGenerated(boolean systemGenerated) {
isSystemGenerated = systemGenerated;
}
public String getTenantIdentifier() {
return tenantIdentifier;
}
public void setTenantIdentifier(String tenantIdentifier) {
this.tenantIdentifier = tenantIdentifier;
}
}
@@ -0,0 +1,78 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.notification.data;
public class NotificationMapperData {
private Long id;
private Long notificationId;
private Long userId;
private boolean isRead;
private String createdAt;
public NotificationMapperData(Long id, Long notificationId, Long userId, boolean isRead, String createdAt) {
this.id = id;
this.notificationId = notificationId;
this.userId = userId;
this.isRead = isRead;
this.createdAt = createdAt;
}
public NotificationMapperData() {}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getNotificationId() {
return notificationId;
}
public void setNotificationId(Long notificationId) {
this.notificationId = notificationId;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public boolean isRead() {
return isRead;
}
public void setRead(boolean read) {
isRead = read;
}
public String getCreatedAt() {
return createdAt;
}
public void setCreatedAt(String createdAt) {
this.createdAt = createdAt;
}
}
@@ -0,0 +1,112 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.notification.domain;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
@Entity
@Table(name = "notification_generator")
public class Notification extends AbstractPersistableCustom<Long> {
@Column(name = "object_type")
private String objectType;
@Column(name = "object_identifier")
private Long objectIdentifier;
@Column(name = "action")
private String action;
@Column(name = "actor")
private Long actorId;
@Column(name = "is_system_generated")
private boolean isSystemGenerated;
@Column(name = "notification_content")
private String notificationContent;
@Column(name = "created_at")
private String createdAt;
public Notification() {}
public Notification(String objectType, Long objectIdentifier, String action, Long actorId, boolean isSystemGenerated,
String notificationContent, String createdAt) {
this.objectType = objectType;
this.objectIdentifier = objectIdentifier;
this.action = action;
this.actorId = actorId;
this.isSystemGenerated = isSystemGenerated;
this.notificationContent = notificationContent;
this.createdAt = createdAt;
}
public String getObjectType() {
return objectType;
}
public void setObjectType(String objectType) {
this.objectType = objectType;
}
public Long getObjectIdentifier() {
return objectIdentifier;
}
public void setObjectIdentifier(Long objectIdentifier) {
this.objectIdentifier = objectIdentifier;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public Long getActor() {
return actorId;
}
public void setActor(Long actor) {
this.actorId = actorId;
}
public boolean isSystemGenerated() {
return isSystemGenerated;
}
public void setSystemGenerated(boolean systemGenerated) {
isSystemGenerated = systemGenerated;
}
public String getNotificationContent() {
return notificationContent;
}
public void setNotificationContent(String notificationContent) {
this.notificationContent = notificationContent;
}
}
@@ -0,0 +1,78 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.notification.domain;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.useradministration.domain.AppUser;
import javax.persistence.*;
@Entity
@Table(name = "notification_mapper")
public class NotificationMapper extends AbstractPersistableCustom<Long> {
@ManyToOne
@JoinColumn(name = "notification_id")
private Notification notification;
@ManyToOne
@JoinColumn(name = "user_id")
private AppUser userId;
@Column(name = "is_read")
private boolean isRead;
@Column(name = "created_at")
private String createdAt;
public NotificationMapper() {}
public NotificationMapper(Notification notification, AppUser userId, boolean isRead, String createdAt) {
this.notification = notification;
this.userId = userId;
this.isRead = isRead;
this.createdAt = createdAt;
}
public Notification getNotification() {
return notification;
}
public void setNotification(Notification notification) {
this.notification = notification;
}
public AppUser getUserId() {
return userId;
}
public void setUserId(AppUser userId) {
this.userId = userId;
}
public boolean isRead() {
return isRead;
}
public void setRead(boolean read) {
isRead = read;
}
}
@@ -0,0 +1,24 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.notification.domain;
import org.springframework.data.jpa.repository.JpaRepository;
public interface NotificationMapperRepository extends JpaRepository<NotificationMapper, Long> {}
@@ -0,0 +1,24 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.notification.domain;
import org.springframework.data.jpa.repository.JpaRepository;
public interface NotificationRepository extends JpaRepository<Notification, Long> {}
@@ -0,0 +1,96 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.notification.eventandlistener;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.infrastructure.security.service.BasicAuthTenantDetailsService;
import org.apache.fineract.notification.data.NotificationData;
import org.apache.fineract.notification.service.NotificationWritePlatformService;
import org.apache.fineract.useradministration.domain.AppUser;
import org.apache.fineract.useradministration.domain.AppUserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.listener.SessionAwareMessageListener;
import org.springframework.stereotype.Service;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.ObjectMessage;
import javax.jms.Session;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@Service
public class NotificationEventListener implements SessionAwareMessageListener {
private final BasicAuthTenantDetailsService basicAuthTenantDetailsService;
private final NotificationWritePlatformService notificationWritePlatformService;
private final AppUserRepository appUserRepository;
@Autowired
public NotificationEventListener(BasicAuthTenantDetailsService basicAuthTenantDetailsService,
NotificationWritePlatformService notificationWritePlatformService,
AppUserRepository appUserRepository) {
this.basicAuthTenantDetailsService = basicAuthTenantDetailsService;
this.notificationWritePlatformService = notificationWritePlatformService;
this.appUserRepository = appUserRepository;
}
@Override
public void onMessage(Message message, Session session) throws JMSException {
if (message instanceof ObjectMessage) {
NotificationData notificationData = (NotificationData) ((ObjectMessage) message).getObject();
final FineractPlatformTenant tenant = this.basicAuthTenantDetailsService
.loadTenantById(notificationData.getTenantIdentifier(), false);
ThreadLocalContextUtil.setTenant(tenant);
Long appUserId = notificationData.getActor();
List<Long> userIds = notificationData.getUserIds();
if (notificationData.getOfficeId() != null) {
List<Long> tempUserIds = new ArrayList<>(userIds);
for (Long userId : tempUserIds) {
AppUser appUser = appUserRepository.findOne(userId);
if (!Objects.equals(appUser.getOffice().getId(), notificationData.getOfficeId())) {
userIds.remove(userId);
}
}
}
if (userIds.contains(appUserId)) {
userIds.remove(appUserId);
}
notificationWritePlatformService.notify(
userIds,
notificationData.getObjectType(),
notificationData.getObjectIdentfier(),
notificationData.getAction(),
notificationData.getActor(),
notificationData.getContent(),
notificationData.isSystemGenerated()
);
}
}
}
@@ -0,0 +1,50 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.notification.eventandlistener;
import org.apache.fineract.notification.data.NotificationData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
import org.springframework.stereotype.Service;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
@Service
public class NotificationEventService {
private final JmsTemplate jmsTemplate;
@Autowired
public NotificationEventService(JmsTemplate jmsTemplate) {
this.jmsTemplate = jmsTemplate;
}
public void broadcastNotification(final Destination destination, final NotificationData notificationData) {
this.jmsTemplate.send(destination, new MessageCreator() {
@Override
public Message createMessage(Session session) throws JMSException {
return session.createObjectMessage(notificationData);
}
});
}
}
@@ -0,0 +1,23 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.notification.service;
public interface NotificationDomainService {
}
@@ -0,0 +1,590 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.notification.service;
import org.apache.activemq.command.ActiveMQQueue;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.notification.data.NotificationData;
import org.apache.fineract.notification.eventandlistener.NotificationEventService;
import org.apache.fineract.portfolio.client.domain.Client;
import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants;
import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_ENTITY;
import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS;
import org.apache.fineract.portfolio.common.service.BusinessEventListner;
import org.apache.fineract.portfolio.common.service.BusinessEventNotifierService;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.apache.fineract.portfolio.savings.DepositAccountType;
import org.apache.fineract.portfolio.savings.domain.FixedDepositAccount;
import org.apache.fineract.portfolio.savings.domain.RecurringDepositAccount;
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction;
import org.apache.fineract.portfolio.shareaccounts.domain.ShareAccount;
import org.apache.fineract.useradministration.domain.AppUser;
import org.apache.fineract.useradministration.domain.AppUserRepository;
import org.apache.fineract.useradministration.domain.Role;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.jms.Queue;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@Service
public class NotificationDomainServiceImpl implements NotificationDomainService {
private final BusinessEventNotifierService businessEventNotifierService;
private final NotificationEventService notificationEventService;
private final AppUserRepository appUserRepository;
private final PlatformSecurityContext context;
@Autowired
public NotificationDomainServiceImpl(final BusinessEventNotifierService businessEventNotifierService,
final NotificationEventService notificationEventService,
final AppUserRepository appUserRepository,
final PlatformSecurityContext context) {
this.businessEventNotifierService = businessEventNotifierService;
this.notificationEventService = notificationEventService;
this.appUserRepository = appUserRepository;
this.context = context;
}
@PostConstruct
public void addListeners() {
businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.CLIENTS_CREATE,
new ClientCreatedListener());
businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.SAVINGS_APPROVE,
new SavingsAccountApprovedListener());
businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.CENTERS_CREATE,
new CenterCreatedListener());
businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.GROUPS_CREATE,
new GroupCreatedListener());
businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.SAVINGS_DEPOSIT,
new SavingsAccountDepositListener());
businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.SHARE_PRODUCT_DIVIDENDS_CREATE,
new ShareProductDividendCreatedListener());
businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.FIXED_DEPOSIT_ACCOUNT_CREATE,
new FixedDepositAccountCreatedListener());
businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.RECURRING_DEPOSIT_ACCOUNT_CREATE,
new RecurringDepositAccountCreatedListener());
businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.SAVINGS_POST_INTEREST,
new SavingsPostInterestListener());
businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_CREATE,
new LoanCreatedListener());
businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_APPROVED,
new LoanApprovedListener());
businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_CLOSE,
new LoanClosedListener());
businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_CLOSE_AS_RESCHEDULE,
new LoanCloseAsRescheduledListener());
businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_MAKE_REPAYMENT,
new LoanMakeRepaymentListener());
businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.LOAN_PRODUCT_CREATE,
new LoanProductCreatedListener());
businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.SAVINGS_CREATE,
new SavingsAccountCreatedListener());
businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.SAVINGS_CLOSE,
new SavingsAccountClosedListener());
businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.SHARE_ACCOUNT_CREATE,
new ShareAccountCreatedListener());
businessEventNotifierService.addBusinessEventPostListners(BUSINESS_EVENTS.SHARE_ACCOUNT_APPROVE,
new ShareAccountApprovedListener());
}
private abstract class NotificationBusinessEventAdapter implements BusinessEventListner {
@Override
public void businessEventToBeExecuted(Map<BusinessEventNotificationConstants.BUSINESS_ENTITY, Object> businessEventEntity) {
//Nothing to do
}
}
private class ClientCreatedListener extends NotificationBusinessEventAdapter {
@Override
public void businessEventWasExecuted(Map<BusinessEventNotificationConstants.BUSINESS_ENTITY, Object> businessEventEntity) {
Client client;
Object entity = businessEventEntity.get(BusinessEventNotificationConstants.BUSINESS_ENTITY.CLIENT);
if (entity != null) {
client = (Client) entity;
buildNotification(
"ACTIVATE_CLIENT",
"client",
client.getId(),
"New client created",
"created",
context.authenticatedUser().getId(),
client.getOffice().getId()
);
}
}
}
private class CenterCreatedListener extends NotificationBusinessEventAdapter {
@Override
public void businessEventWasExecuted(Map<BusinessEventNotificationConstants.BUSINESS_ENTITY, Object> businessEventEntity) {
CommandProcessingResult commandProcessingResult;
Object entity = businessEventEntity.get(BUSINESS_ENTITY.GROUP);
if (entity != null) {
commandProcessingResult = (CommandProcessingResult) entity;
buildNotification(
"ACTIVATE_CENTER",
"center",
commandProcessingResult.getGroupId(),
"New center created",
"created",
context.authenticatedUser().getId(),
commandProcessingResult.getOfficeId()
);
}
}
}
private class GroupCreatedListener extends NotificationBusinessEventAdapter {
@Override
public void businessEventWasExecuted(Map<BusinessEventNotificationConstants.BUSINESS_ENTITY, Object> businessEventEntity) {
CommandProcessingResult commandProcessingResult;
Object entity = businessEventEntity.get(BUSINESS_ENTITY.GROUP);
if (entity != null) {
commandProcessingResult = (CommandProcessingResult) entity;
buildNotification(
"ACTIVATE_GROUP",
"group",
commandProcessingResult.getGroupId(),
"New group created",
"created",
context.authenticatedUser().getId(),
commandProcessingResult.getOfficeId()
);
}
}
}
private class SavingsAccountDepositListener extends NotificationBusinessEventAdapter {
@Override
public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
SavingsAccountTransaction savingsAccountTransaction;
Object entity = businessEventEntity.get(BUSINESS_ENTITY.SAVINGS_TRANSACTION);
if (entity != null) {
savingsAccountTransaction = (SavingsAccountTransaction) entity;
buildNotification(
"READ_SAVINGSACCOUNT",
"savingsAccount",
savingsAccountTransaction.getSavingsAccount().getId(),
"Deposit made",
"depositMade",
context.authenticatedUser().getId(),
savingsAccountTransaction.getSavingsAccount().officeId()
);
}
}
}
private class ShareProductDividendCreatedListener extends NotificationBusinessEventAdapter {
@Override
public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
Long shareProductId;
Object entity = businessEventEntity.get(BUSINESS_ENTITY.SHARE_PRODUCT);
if (entity != null) {
shareProductId = (Long) entity;
buildNotification(
"READ_DIVIDEND_SHAREPRODUCT",
"shareProduct",
shareProductId,
"Dividend posted to account",
"dividendPosted",
context.authenticatedUser().getId(),
context.authenticatedUser().getOffice().getId()
);
}
}
}
private class FixedDepositAccountCreatedListener extends NotificationBusinessEventAdapter {
@Override
public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
FixedDepositAccount fixedDepositAccount;
Object entity = businessEventEntity.get(BUSINESS_ENTITY.DEPOSIT_ACCOUNT);
if (entity != null) {
fixedDepositAccount = (FixedDepositAccount) entity;
buildNotification(
"APPROVE_FIXEDDEPOSITACCOUNT",
"fixedDeposit",
fixedDepositAccount.getId(),
"New fixed deposit account created",
"created",
context.authenticatedUser().getId(),
fixedDepositAccount.officeId()
);
}
}
}
private class RecurringDepositAccountCreatedListener extends NotificationBusinessEventAdapter {
@Override
public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
RecurringDepositAccount recurringDepositAccount;
Object entity = businessEventEntity.get(BUSINESS_ENTITY.DEPOSIT_ACCOUNT);
if (entity != null) {
recurringDepositAccount = (RecurringDepositAccount) entity;
buildNotification(
"APPROVE_RECURRINGDEPOSITACCOUNT",
"recurringDepositAccount",
recurringDepositAccount.getId(),
"New recurring deposit account created",
"created",
context.authenticatedUser().getId(),
recurringDepositAccount.officeId()
);
}
}
}
private class SavingsAccountApprovedListener extends NotificationBusinessEventAdapter {
@Override
public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
SavingsAccount savingsAccount;
Object entity = businessEventEntity.get(BUSINESS_ENTITY.SAVING);
if (entity != null) {
savingsAccount = (SavingsAccount) entity;
if (savingsAccount.depositAccountType().equals(DepositAccountType.FIXED_DEPOSIT)) {
buildNotification(
"ACTIVATE_FIXEDDEPOSITACCOUNT",
"fixedDeposit",
savingsAccount.getId(),
"Fixed deposit account approved",
"approved",
context.authenticatedUser().getId(),
savingsAccount.officeId()
);
} else if (savingsAccount.depositAccountType().equals(DepositAccountType.RECURRING_DEPOSIT)) {
buildNotification(
"ACTIVATE_RECURRINGDEPOSITACCOUNT",
"recurringDepositAccount",
savingsAccount.getId(),
"Recurring deposit account approved",
"approved",
context.authenticatedUser().getId(),
savingsAccount.officeId()
);
} else if (savingsAccount.depositAccountType().equals(DepositAccountType.SAVINGS_DEPOSIT)) {
buildNotification(
"ACTIVATE_SAVINGSACCOUNT",
"savingsAccount",
savingsAccount.getId(),
"Savings account approved",
"approved",
context.authenticatedUser().getId(),
savingsAccount.officeId()
);
}
}
}
}
private class SavingsPostInterestListener extends NotificationBusinessEventAdapter {
@Override
public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
SavingsAccount savingsAccount;
Object entity = businessEventEntity.get(BUSINESS_ENTITY.SAVING);
if (entity != null) {
savingsAccount = (SavingsAccount) entity;
buildNotification(
"READ_SAVINGSACCOUNT",
"savingsAccount",
savingsAccount.getId(),
"Interest posted to account",
"interestPosted",
context.authenticatedUser().getId(),
savingsAccount.officeId()
);
}
}
}
private class LoanCreatedListener extends NotificationBusinessEventAdapter {
@Override
public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
Loan loan;
Object entity = businessEventEntity.get(BUSINESS_ENTITY.LOAN);
if (entity != null) {
loan = (Loan) entity;
buildNotification(
"APPROVE_LOAN",
"loan",
loan.getId(),
"New loan created",
"created",
context.authenticatedUser().getId(),
loan.getOfficeId()
);
}
}
}
private class LoanApprovedListener extends NotificationBusinessEventAdapter {
@Override
public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
Loan loan;
Object entity = businessEventEntity.get(BUSINESS_ENTITY.LOAN);
if (entity != null) {
loan = (Loan) entity;
buildNotification(
"DISBURSE_LOAN",
"loan",
loan.getId(),
"New loan approved",
"approved",
context.authenticatedUser().getId(),
loan.getOfficeId()
);
}
}
}
private class LoanClosedListener extends NotificationBusinessEventAdapter {
@Override
public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
Loan loan;
Object entity = businessEventEntity.get(BUSINESS_ENTITY.LOAN);
if (entity != null) {
loan = (Loan) entity;
buildNotification(
"READ_LOAN",
"loan",
loan.getId(),
"Loan closed",
"loanClosed",
context.authenticatedUser().getId(),
loan.getOfficeId()
);
}
}
}
private class LoanCloseAsRescheduledListener extends NotificationBusinessEventAdapter {
@Override
public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
Loan loan;
Object entity = businessEventEntity.get(BUSINESS_ENTITY.LOAN);
if (entity != null) {
loan = (Loan) entity;
buildNotification(
"READ_Rescheduled Loans",
"loan",
loan.getId(),
"Loan has been rescheduled",
"loanRescheduled",
context.authenticatedUser().getId(),
loan.getOfficeId()
);
}
}
}
private class LoanMakeRepaymentListener extends NotificationBusinessEventAdapter {
@Override
public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
Loan loan;
Object entity = businessEventEntity.get(BUSINESS_ENTITY.LOAN);
if (entity != null) {
loan = (Loan) entity;
buildNotification(
"READ_LOAN",
"loan",
loan.getId(),
"Repayment made",
"repaymentMade",
context.authenticatedUser().getId(),
loan.getOfficeId()
);
}
}
}
private class LoanProductCreatedListener extends NotificationBusinessEventAdapter {
@Override
public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
LoanProduct loanProduct;
Object entity = businessEventEntity.get(BUSINESS_ENTITY.LOAN_PRODUCT);
if (entity != null) {
loanProduct = (LoanProduct) entity;
buildNotification(
"READ_LOANPRODUCT",
"loanProduct",
loanProduct.getId(),
"New loan product created",
"created",
context.authenticatedUser().getId(),
context.authenticatedUser().getOffice().getId()
);
}
}
}
private class SavingsAccountCreatedListener extends NotificationBusinessEventAdapter {
@Override
public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
SavingsAccount savingsAccount;
Object entity = businessEventEntity.get(BUSINESS_ENTITY.SAVING);
if (entity != null) {
savingsAccount = (SavingsAccount) entity;
buildNotification(
"APPROVE_SAVINGSACCOUNT",
"savingsAccount",
savingsAccount.getId(),
"New savings account created",
"created",
context.authenticatedUser().getId(),
savingsAccount.officeId()
);
}
}
}
private class SavingsAccountClosedListener extends NotificationBusinessEventAdapter {
@Override
public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
SavingsAccount savingsAccount;
Object entity = businessEventEntity.get(BUSINESS_ENTITY.SAVING);
if (entity != null) {
savingsAccount = (SavingsAccount) entity;
buildNotification(
"READ_SAVINGSACCOUNT",
"savingsAccount",
savingsAccount.getId(),
"Savings has gone into dormant",
"closed",
context.authenticatedUser().getId(),
savingsAccount.officeId()
);
}
}
}
private class ShareAccountCreatedListener extends NotificationBusinessEventAdapter {
@Override
public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
ShareAccount shareAccount;
Object entity = businessEventEntity.get(BUSINESS_ENTITY.SHARE_ACCOUNT);
if (entity != null) {
shareAccount = (ShareAccount) entity;
buildNotification(
"APPROVE_SHAREACCOUNT",
"shareAccount",
shareAccount.getId(),
"New share account created",
"created",
context.authenticatedUser().getId(),
shareAccount.getOfficeId()
);
}
}
}
private class ShareAccountApprovedListener extends NotificationBusinessEventAdapter {
@Override
public void businessEventWasExecuted(Map<BUSINESS_ENTITY, Object> businessEventEntity) {
ShareAccount shareAccount;
Object entity = businessEventEntity.get(BUSINESS_ENTITY.SHARE_ACCOUNT);
if (entity != null) {
shareAccount = (ShareAccount) entity;
buildNotification(
"ACTIVATE_SHAREACCOUNT",
"shareAccount",
shareAccount.getId(),
"Share account approved",
"approved",
context.authenticatedUser().getId(),
shareAccount.getOfficeId()
);
}
}
}
private void buildNotification(String permission, String objectType,
Long objectIdentifier, String notificationContent, String eventType,
Long appUserId, Long officeId) {
String tenantIdentifier = ThreadLocalContextUtil.getTenant().getTenantIdentifier();
Queue queue = new ActiveMQQueue("NotificationQueue");
List<Long> userIds = retrieveUsersWithSpecificPermission(permission);
NotificationData notificationData = new NotificationData(
objectType,
objectIdentifier,
eventType,
appUserId,
notificationContent,
false,
tenantIdentifier,
officeId,
userIds
);
notificationEventService.broadcastNotification(queue, notificationData);
}
private List<Long> retrieveUsersWithSpecificPermission(String permission) {
List<AppUser> appUsers = appUserRepository.findAll();
List<Long> userIds = new ArrayList<>();
for (AppUser appUser : appUsers) {
Set<Role> roles = appUser.getRoles();
for (Role role : roles) {
if (role.hasPermissionTo(permission)) {
if (!(userIds.contains(appUser.getId()))) {
userIds.add(appUser.getId());
}
}
}
}
return userIds;
}
}
@@ -0,0 +1,35 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.notification.service;
import org.apache.fineract.notification.domain.Notification;
import java.util.List;
public interface NotificationGeneratorReadRepositoryWrapper {
Notification findById(Long id);
List<Notification> fetchAllNotifications();
void delete(Long id);
}
@@ -0,0 +1,53 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.notification.service;
import org.apache.fineract.notification.domain.Notification;
import org.apache.fineract.notification.domain.NotificationRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class NotificationGeneratorReadRepositoryWrapperImpl implements NotificationGeneratorReadRepositoryWrapper {
private final NotificationRepository notificationRepository;
@Autowired
public NotificationGeneratorReadRepositoryWrapperImpl(NotificationRepository notificationRepository) {
this.notificationRepository = notificationRepository;
}
@Override
public Notification findById(Long id) {
return this.notificationRepository.findOne(id);
}
@Override
public List<Notification> fetchAllNotifications() {
return this.notificationRepository.findAll();
}
@Override
public void delete(Long id) {
this.notificationRepository.delete(id);
}
}
@@ -0,0 +1,27 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.notification.service;
import org.apache.fineract.notification.domain.Notification;
public interface NotificationGeneratorWritePlatformService {
Long create(Notification notification);
}
@@ -0,0 +1,41 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.notification.service;
import org.apache.fineract.notification.domain.Notification;
import org.apache.fineract.notification.domain.NotificationRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class NotificationGeneratorWritePlatformServiceImpl implements NotificationGeneratorWritePlatformService {
private final NotificationRepository notificationRepository;
@Autowired
public NotificationGeneratorWritePlatformServiceImpl(NotificationRepository notificationRepository) {
this.notificationRepository = notificationRepository;
}
@Override
public Long create(Notification notification) {
this.notificationRepository.save(notification);
return notification.getId();
}
}
@@ -0,0 +1,34 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.notification.service;
import org.apache.fineract.notification.domain.NotificationMapper;
import java.util.List;
public interface NotificationMapperReadRepositoryWrapper {
NotificationMapper findById(Long id);
List<NotificationMapper> fetchAllNotifications();
void delete(Long id);
}
@@ -0,0 +1,53 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.notification.service;
import org.apache.fineract.notification.domain.NotificationMapper;
import org.apache.fineract.notification.domain.NotificationMapperRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class NotificationMapperReadRepositoryWrapperImpl implements NotificationMapperReadRepositoryWrapper {
private final NotificationMapperRepository notificationMapperRepository;
@Autowired
public NotificationMapperReadRepositoryWrapperImpl(NotificationMapperRepository notificationMapperRepository) {
this.notificationMapperRepository = notificationMapperRepository;
}
@Override
public NotificationMapper findById(Long id) {
return this.notificationMapperRepository.findOne(id);
}
@Override
public List<NotificationMapper> fetchAllNotifications() {
return this.notificationMapperRepository.findAll();
}
@Override
public void delete(Long id) {
this.notificationMapperRepository.delete(id);
}
}
@@ -0,0 +1,27 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.notification.service;
import org.apache.fineract.notification.domain.NotificationMapper;
public interface NotificationMapperWritePlatformService {
Long create(NotificationMapper notificationMapper);
}
@@ -0,0 +1,41 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.notification.service;
import org.apache.fineract.notification.domain.NotificationMapper;
import org.apache.fineract.notification.domain.NotificationMapperRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class NotificationMapperWritePlatformServiceImpl implements NotificationMapperWritePlatformService {
private final NotificationMapperRepository notificationMapperRepository;
@Autowired
public NotificationMapperWritePlatformServiceImpl(NotificationMapperRepository notificationMapperRepository) {
this.notificationMapperRepository = notificationMapperRepository;
}
@Override
public Long create(NotificationMapper notificationMapper) {
this.notificationMapperRepository.save(notificationMapper);
return notificationMapper.getId();
}
}
@@ -0,0 +1,34 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.notification.service;
import org.apache.fineract.infrastructure.core.service.Page;
import org.apache.fineract.infrastructure.core.service.SearchParameters;
import org.apache.fineract.notification.data.NotificationData;
public interface NotificationReadPlatformService {
boolean hasUnreadNotifications(Long appUserId);
Page<NotificationData> getAllUnreadNotifications(SearchParameters searchParameters);
Page<NotificationData> getAllNotifications(SearchParameters searchParameters);
void updateNotificationReadStatus();
}
@@ -0,0 +1,219 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.notification.service;
import org.apache.fineract.infrastructure.core.service.*;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.notification.cache.CacheNotificationResponseHeader;
import org.apache.fineract.notification.data.NotificationData;
import org.apache.fineract.notification.data.NotificationMapperData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
@Service
public class NotificationReadPlatformServiceImpl implements NotificationReadPlatformService {
private final JdbcTemplate jdbcTemplate;
private final PlatformSecurityContext context;
private final PaginationHelper<NotificationData> paginationHelper = new PaginationHelper<>();
private final NotificationDataRow notificationDataRow = new NotificationDataRow();
private final NotificationMapperRow notificationMapperRow = new NotificationMapperRow();
private HashMap<Long, HashMap<Long, CacheNotificationResponseHeader>>
tenantNotificationResponseHeaderCache = new HashMap<>();
@Autowired
public NotificationReadPlatformServiceImpl(final RoutingDataSource dataSource, final PlatformSecurityContext context) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.context = context;
}
@Override
public boolean hasUnreadNotifications(Long appUserId) {
Long tenantId = ThreadLocalContextUtil.getTenant().getId();
Long now = System.currentTimeMillis()/1000L;
if (this.tenantNotificationResponseHeaderCache.containsKey(tenantId)) {
HashMap<Long, CacheNotificationResponseHeader> notificationResponseHeaderCache =
this.tenantNotificationResponseHeaderCache.get(tenantId);
if (notificationResponseHeaderCache.containsKey(appUserId)) {
Long lastFetch = notificationResponseHeaderCache.get(appUserId).getLastFetch();
if ((now - lastFetch) > 1) {
return this.createUpdateCacheValue(appUserId, now, notificationResponseHeaderCache);
} else {
return notificationResponseHeaderCache.get(appUserId).hasNotifications();
}
} else {
return this.createUpdateCacheValue(appUserId, now, notificationResponseHeaderCache);
}
} else {
return this.initializeTenantNotificationResponseHeaderCache(tenantId, now, appUserId);
}
}
private boolean initializeTenantNotificationResponseHeaderCache(Long tenantId, Long now, Long appUserId) {
HashMap<Long, CacheNotificationResponseHeader> notificationResponseHeaderCache = new HashMap<>();
this.tenantNotificationResponseHeaderCache.put(tenantId, notificationResponseHeaderCache);
return this.createUpdateCacheValue(appUserId, now, notificationResponseHeaderCache);
}
private boolean createUpdateCacheValue(Long appUserId, Long now, HashMap<Long,
CacheNotificationResponseHeader> notificationResponseHeaderCache) {
boolean hasNotifications;
Long tenantId = ThreadLocalContextUtil.getTenant().getId();
CacheNotificationResponseHeader cacheNotificationResponseHeader;
hasNotifications = checkForUnreadNotifications(appUserId);
cacheNotificationResponseHeader = new CacheNotificationResponseHeader(hasNotifications, now);
notificationResponseHeaderCache.put(appUserId, cacheNotificationResponseHeader);
this.tenantNotificationResponseHeaderCache.put(tenantId, notificationResponseHeaderCache);
return hasNotifications;
}
private boolean checkForUnreadNotifications(Long appUserId) {
String sql = "SELECT id, notification_id as notificationId, user_id as userId, is_read as isRead, created_at " +
"as createdAt FROM notification_mapper WHERE user_id = ? AND is_read = false";
List<NotificationMapperData > notificationMappers = this.jdbcTemplate.query(
sql,
notificationMapperRow,
appUserId);
return notificationMappers.size() > 0;
}
@Override
public void updateNotificationReadStatus() {
final Long appUserId = context.authenticatedUser().getId();
String sql = "UPDATE notification_mapper SET is_read = true WHERE is_read = false and user_id = ?";
this.jdbcTemplate.update(sql, appUserId);
}
@Override
public Page<NotificationData> getAllUnreadNotifications(final SearchParameters searchParameters) {
final Long appUserId = context.authenticatedUser().getId();
String sql = "SELECT SQL_CALC_FOUND_ROWS ng.id as id, nm.user_id as userId, ng.object_type as objectType, " +
"ng.object_identifier as objectId, ng.actor as actor, ng.action action, ng.notification_content " +
"as content, ng.is_system_generated as isSystemGenerated, nm.created_at as createdAt " +
"FROM notification_mapper nm INNER JOIN notification_generator ng ON nm.notification_id = ng.id " +
"WHERE nm.user_id = ? AND nm.is_read = false order by nm.created_at desc";
return getNotificationDataPage(searchParameters, appUserId, sql);
}
@Override
public Page<NotificationData> getAllNotifications(SearchParameters searchParameters) {
final Long appUserId = context.authenticatedUser().getId();
String sql = "SELECT SQL_CALC_FOUND_ROWS ng.id as id, nm.user_id as userId, ng.object_type as objectType, " +
"ng.object_identifier as objectId, ng.actor as actor, ng.action action, ng.notification_content " +
"as content, ng.is_system_generated as isSystemGenerated, nm.created_at as createdAt " +
"FROM notification_mapper nm INNER JOIN notification_generator ng ON nm.notification_id = ng.id " +
"WHERE nm.user_id = ? order by nm.created_at desc";
return getNotificationDataPage(searchParameters, appUserId, sql);
}
private Page<NotificationData> getNotificationDataPage(SearchParameters searchParameters, Long appUserId, String sql) {
final StringBuilder sqlBuilder = new StringBuilder(200);
sqlBuilder.append(sql);
if (searchParameters.isOrderByRequested()) {
sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
if (searchParameters.isSortOrderProvided()) {
sqlBuilder.append(' ').append(searchParameters.getSortOrder());
}
}
if (searchParameters.isLimited()) {
sqlBuilder.append(" limit ").append(searchParameters.getLimit());
if (searchParameters.isOffset()) {
sqlBuilder.append(" offset ").append(searchParameters.getOffset());
}
}
final String sqlCountRows = "SELECT FOUND_ROWS()";
Object[] params = new Object[]{appUserId};
return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows, sqlBuilder.toString(),
params, this.notificationDataRow);
}
private static final class NotificationMapperRow implements RowMapper<NotificationMapperData> {
@Override
public NotificationMapperData mapRow(ResultSet rs, int rowNum) throws SQLException {
NotificationMapperData notificationMapperData = new NotificationMapperData();
final Long id = rs.getLong("id");
notificationMapperData.setId(id);
final Long notificationId = rs.getLong("notificationId");
notificationMapperData.setNotificationId(notificationId);
final Long userId = rs.getLong("userId");
notificationMapperData.setUserId(userId);
final boolean isRead = rs.getBoolean("isRead");
notificationMapperData.setRead(isRead);
final String createdAt = rs.getString("createdAt");
notificationMapperData.setCreatedAt(createdAt);
return notificationMapperData;
}
}
private static final class NotificationDataRow implements RowMapper<NotificationData> {
@Override
public NotificationData mapRow(ResultSet rs, int rowNum) throws SQLException {
NotificationData notificationData = new NotificationData();
final Long id = rs.getLong("id");
notificationData.setId(id);
final String objectType = rs.getString("objectType");
notificationData.setObjectType(objectType);
final Long objectId = rs.getLong("objectId");
notificationData.entifier(objectId);
final Long actorId = rs.getLong("actor");
notificationData.setActor(actorId);
final String action = rs.getString("action");
notificationData.setAction(action);
final String content = rs.getString("content");
notificationData.setContent(content);
final boolean isSystemGenerated = rs.getBoolean("isSystemGenerated");
notificationData.setSystemGenerated(isSystemGenerated);
final String createdAt = rs.getString("createdAt");
notificationData.setCreatedAt(createdAt);
return notificationData;
}
}
}
@@ -0,0 +1,29 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.notification.service;
import java.util.List;
public interface NotificationWritePlatformService {
Long notify(Long userId, String objectType, Long objectId, String action,
Long actorId, String notificationContent, boolean isSystemGenerated);
Long notify(List<Long> userIds, String objectType, Long objectId, String action,
Long actorId, String notificationContent, boolean isSystemGenerated);
}
@@ -0,0 +1,129 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.notification.service;
import org.apache.fineract.notification.domain.Notification;
import org.apache.fineract.notification.domain.NotificationMapper;
import org.apache.fineract.useradministration.domain.AppUser;
import org.apache.fineract.useradministration.domain.AppUserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@Service
public class NotificationWritePlatformServiceImpl implements NotificationWritePlatformService {
private final NotificationGeneratorWritePlatformService notificationGeneratorWritePlatformService;
private final NotificationGeneratorReadRepositoryWrapper notificationGeneratorReadRepositoryWrapper;
private final AppUserRepository appUserRepository;
private final NotificationMapperWritePlatformService notificationMapperWritePlatformService;
@Autowired
public NotificationWritePlatformServiceImpl(
final NotificationGeneratorWritePlatformService notificationGeneratorWritePlatformService,
final NotificationGeneratorReadRepositoryWrapper notificationGeneratorReadRepositoryWrapper,
final AppUserRepository appUserRepository,
final NotificationMapperWritePlatformService notificationMapperWritePlatformService) {
this.notificationGeneratorWritePlatformService = notificationGeneratorWritePlatformService;
this.notificationGeneratorReadRepositoryWrapper = notificationGeneratorReadRepositoryWrapper;
this.appUserRepository = appUserRepository;
this.notificationMapperWritePlatformService = notificationMapperWritePlatformService;
}
@Override
public Long notify(Long userId, String objectType, Long objectIdentifier, String action,
Long actorId, String notificationContent, boolean isSystemGenerated) {
Long generatedNotificationId = insertIntoNotificationGenerator(objectType, objectIdentifier, action,
actorId, notificationContent, isSystemGenerated);
insertIntoNotificationMapper(userId, generatedNotificationId);
return generatedNotificationId;
}
private Long insertIntoNotificationMapper(Long userId, Long generatedNotificationId) {
AppUser appUser = this.appUserRepository.findOne(userId);
NotificationMapper notificationMapper = new NotificationMapper(
this.notificationGeneratorReadRepositoryWrapper.findById(generatedNotificationId),
appUser,
false,
getCurrentDateTime()
);
this.notificationMapperWritePlatformService.create(notificationMapper);
return notificationMapper.getId();
}
private Long insertIntoNotificationGenerator(String objectType, Long objectIdentifier, String action,
Long actorId, String notificationContent,
boolean isSystemGenerated) {
Notification notification = new Notification(
objectType,
objectIdentifier,
action,
actorId,
isSystemGenerated,
notificationContent,
getCurrentDateTime()
);
return this.notificationGeneratorWritePlatformService.create(notification);
}
@Override
public Long notify(List<Long> userIds, String objectType, Long objectId, String action,
Long actorId, String notificationContent, boolean isSystemGenerated) {
Long generatedNotificationId = insertIntoNotificationGenerator(objectType, objectId, action,
actorId, notificationContent, isSystemGenerated);
insertIntoNotificationMapper(userIds, generatedNotificationId);
return generatedNotificationId;
}
private List<Long> insertIntoNotificationMapper(List<Long> userIds, Long generatedNotificationId) {
List<Long> mappedIds = new ArrayList<>();
for (Long userId : userIds) {
AppUser appUser = this.appUserRepository.findOne(userId);
NotificationMapper notificationMapper = new NotificationMapper(
this.notificationGeneratorReadRepositoryWrapper.findById(generatedNotificationId),
appUser,
false,
getCurrentDateTime()
);
this.notificationMapperWritePlatformService.create(notificationMapper);
mappedIds.add(notificationMapper.getId());
}
return mappedIds;
}
private String getCurrentDateTime() {
Date date = new Date();
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return formatter.format(date);
}
}
@@ -342,6 +342,9 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
command.arrayOfParameterNamed(ClientApiConstants.datatables));
}
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.CLIENTS_CREATE,
constructEntityMap(BUSINESS_ENTITY.CLIENT, newClient));
this.entityDatatableChecksWritePlatformService.runTheCheck(newClient.getId(), EntityTables.CLIENT.getName(),
StatusEnum.CREATE.getCode().longValue(), EntityTables.CLIENT.getForeignKeyColumnNameOnDatatable());
return new CommandProcessingResultBuilder() //
@@ -32,10 +32,13 @@ public class BusinessEventNotificationConstants {
"loan_update_charge"), LOAN_WAIVE_CHARGE("loan_waive_charge"), LOAN_DELETE_CHARGE("loan_delete_charge"), LOAN_CHARGE_PAYMENT(
"loan_charge_payment"), LOAN_INITIATE_TRANSFER("loan_initiate_transfer"), LOAN_ACCEPT_TRANSFER("loan_accept_transfer"), LOAN_WITHDRAW_TRANSFER(
"loan_withdraw_transfer"), LOAN_REJECT_TRANSFER("loan_reject_transfer"), LOAN_REASSIGN_OFFICER("loan_reassign_officer"), LOAN_REMOVE_OFFICER(
"loan_remove_officer"), LOAN_APPLY_OVERDUE_CHARGE("loan_apply_overdue_charge"), LOAN_INTEREST_RECALCULATION(
"loan_interest_recalculation"), LOAN_REFUND("loan_refund"), LOAN_FORECLOSURE("loan_foreclosure"), SAVINGS_ACTIVATE(
"savings_activated"), SAVINGS_REJECT("savings_reject"), SAVINGS_DEPOSIT("savings_deposit"), SAVINGS_WITHDRAWAL(
"savings_withdrawal"), CLIENTS_ACTIVATE("clients_activate"), CLIENTS_REJECT("clients_reject");
"loan_remove_officer"), LOAN_APPLY_OVERDUE_CHARGE("loan_apply_overdue_charge"), LOAN_INTEREST_RECALCULATION("loan_interest_recalculation"),
LOAN_REFUND("loan_refund"), LOAN_FORECLOSURE("loan_foreclosure"), LOAN_CREATE("loan_create"), LOAN_PRODUCT_CREATE("loan_product_create"), SAVINGS_ACTIVATE("savings_activated"),
SAVINGS_REJECT("savings_reject"), SAVINGS_POST_INTEREST("savings_post_interest"), SAVINGS_DEPOSIT("savings_deposit"), SAVINGS_CLOSE("savings_close"),
SAVINGS_WITHDRAWAL("savings_withdrawal"), SAVINGS_APPROVE("savings_approve"), SAVINGS_CREATE("savings_create"), CLIENTS_ACTIVATE("clients_activate"), SHARE_ACCOUNT_CREATE("share_account_create"),
CLIENTS_REJECT("clients_reject"), CLIENTS_CREATE("clients_create"),CENTERS_CREATE("centers_create"), GROUPS_CREATE("groups_create"),
SHARE_PRODUCT_DIVIDENDS_CREATE("share_product_dividends_create"),FIXED_DEPOSIT_ACCOUNT_CREATE("fixed_deposit_account_create"),
SHARE_ACCOUNT_APPROVE("share_account_approve"), RECURRING_DEPOSIT_ACCOUNT_CREATE("recurring_deposit_account_create");
private final String value;
@@ -61,7 +64,8 @@ public class BusinessEventNotificationConstants {
public static enum BUSINESS_ENTITY {
LOAN("loan"), LOAN_TRANSACTION("loan_transaction"), LOAN_CHARGE("loan_charge"), LOAN_ADJUSTED_TRANSACTION(
"loan_adjusted_transaction"), SAVING("saving"), CLIENT("client"), SAVINGS_TRANSACTION("Savings Transaction");
"loan_adjusted_transaction"), SAVING("saving"), CLIENT("client"), SAVINGS_TRANSACTION("Savings Transaction"), GROUP("group"),
SHARE_ACCOUNT("share_account"), SHARE_PRODUCT("share_product"), DEPOSIT_ACCOUNT("deposit_account"), LOAN_PRODUCT("loan_product");
private final String value;
@@ -52,6 +52,8 @@ import org.apache.fineract.portfolio.client.domain.AccountNumberGenerator;
import org.apache.fineract.portfolio.client.domain.Client;
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
import org.apache.fineract.portfolio.client.service.LoanStatusMapper;
import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants;
import org.apache.fineract.portfolio.common.service.BusinessEventNotifierService;
import org.apache.fineract.portfolio.group.api.GroupingTypesApiConstants;
import org.apache.fineract.portfolio.group.domain.*;
import org.apache.fineract.portfolio.group.exception.*;
@@ -72,6 +74,8 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_ENTITY;
import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS;
@Service
public class GroupingTypesWritePlatformServiceJpaRepositoryImpl implements GroupingTypesWritePlatformService {
@@ -95,6 +99,7 @@ public class GroupingTypesWritePlatformServiceJpaRepositoryImpl implements Group
private final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository;
private final AccountNumberGenerator accountNumberGenerator;
private final EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService;
private final BusinessEventNotifierService businessEventNotifierService;
@Autowired
public GroupingTypesWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
@@ -106,7 +111,8 @@ public class GroupingTypesWritePlatformServiceJpaRepositoryImpl implements Group
final CalendarInstanceRepository calendarInstanceRepository, final ConfigurationDomainService configurationDomainService,
final LoanRepositoryWrapper loanRepositoryWrapper,
final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository, final AccountNumberGenerator accountNumberGenerator,
final EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService) {
final EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService,
final BusinessEventNotifierService businessEventNotifierService) {
this.context = context;
this.groupRepository = groupRepository;
this.clientRepositoryWrapper = clientRepositoryWrapper;
@@ -124,6 +130,7 @@ public class GroupingTypesWritePlatformServiceJpaRepositoryImpl implements Group
this.accountNumberFormatRepository = accountNumberFormatRepository;
this.accountNumberGenerator = accountNumberGenerator;
this.entityDatatableChecksWritePlatformService = entityDatatableChecksWritePlatformService;
this.businessEventNotifierService = businessEventNotifierService;
}
private CommandProcessingResult createGroupingType(final JsonCommand command, final GroupTypes groupingType, final Long centerId) {
@@ -263,7 +270,13 @@ public class GroupingTypesWritePlatformServiceJpaRepositoryImpl implements Group
this.fromApiJsonDeserializer.validateForCreateCenter(command);
final Long centerId = null;
return createGroupingType(command, GroupTypes.CENTER, centerId);
CommandProcessingResult commandProcessingResult = createGroupingType(command, GroupTypes.CENTER, centerId);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.CENTERS_CREATE,
constructEntityMap(BUSINESS_ENTITY.GROUP, commandProcessingResult));
return commandProcessingResult;
}
@Transactional
@@ -276,7 +289,12 @@ public class GroupingTypesWritePlatformServiceJpaRepositoryImpl implements Group
this.fromApiJsonDeserializer.validateForCreateGroup(command);
}
return createGroupingType(command, GroupTypes.GROUP, centerId);
CommandProcessingResult commandProcessingResult = createGroupingType(command, GroupTypes.GROUP, centerId);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BusinessEventNotificationConstants.BUSINESS_EVENTS.GROUPS_CREATE,
constructEntityMap(BUSINESS_ENTITY.GROUP, commandProcessingResult));
return commandProcessingResult;
}
@Transactional
@@ -961,4 +979,10 @@ public class GroupingTypesWritePlatformServiceJpaRepositoryImpl implements Group
}
}
}
private Map<BusinessEventNotificationConstants.BUSINESS_ENTITY, Object> constructEntityMap(final BUSINESS_ENTITY entityEvent, Object entity) {
Map<BUSINESS_ENTITY, Object> map = new HashMap<>(1);
map.put(entityEvent, entity);
return map;
}
}
@@ -394,6 +394,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
EntityTables.LOAN.getName(), StatusEnum.CREATE.getCode().longValue(),
EntityTables.LOAN.getForeignKeyColumnNameOnDatatable(), newLoanApplication.productId());
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_CREATE,
constructEntityMap(BUSINESS_ENTITY.LOAN, newLoanApplication));
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
@@ -128,6 +128,7 @@ import org.springframework.util.CollectionUtils;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
@Service
public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatformService {
@@ -1172,7 +1173,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.loanAccountDomainService.recalculateAccruals(loan);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_CLOSE,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
// disable all active standing instructions linked to the loan
this.loanAccountDomainService.disableStandingInstructionsLinkedToClosedLoan(loan);
@@ -19,6 +19,7 @@
package org.apache.fineract.portfolio.loanproduct.service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -35,6 +36,8 @@ import org.apache.fineract.infrastructure.entityaccess.service.FineractEntityAcc
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.charge.domain.Charge;
import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants;
import org.apache.fineract.portfolio.common.service.BusinessEventNotifierService;
import org.apache.fineract.portfolio.floatingrates.domain.FloatingRate;
import org.apache.fineract.portfolio.floatingrates.domain.FloatingRateRepositoryWrapper;
import org.apache.fineract.portfolio.fund.domain.Fund;
@@ -43,6 +46,8 @@ import org.apache.fineract.portfolio.fund.exception.FundNotFoundException;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionProcessingStrategyRepository;
import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionProcessingStrategyNotFoundException;
import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_ENTITY;
import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AprCalculator;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
@@ -78,6 +83,7 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
private final FineractEntityAccessUtil fineractEntityAccessUtil;
private final FloatingRateRepositoryWrapper floatingRateRepository;
private final LoanRepositoryWrapper loanRepositoryWrapper;
private final BusinessEventNotifierService businessEventNotifierService;
@Autowired
public LoanProductWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
@@ -88,7 +94,8 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
final ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService,
final FineractEntityAccessUtil fineractEntityAccessUtil,
final FloatingRateRepositoryWrapper floatingRateRepository,
final LoanRepositoryWrapper loanRepositoryWrapper) {
final LoanRepositoryWrapper loanRepositoryWrapper,
final BusinessEventNotifierService businessEventNotifierService) {
this.context = context;
this.fromApiJsonDeserializer = fromApiJsonDeserializer;
this.loanProductRepository = loanProductRepository;
@@ -100,6 +107,7 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
this.fineractEntityAccessUtil = fineractEntityAccessUtil;
this.floatingRateRepository = floatingRateRepository;
this.loanRepositoryWrapper = loanRepositoryWrapper;
this.businessEventNotifierService = businessEventNotifierService;
}
@Transactional
@@ -139,6 +147,9 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
fineractEntityAccessUtil.checkConfigurationAndAddProductResrictionsForUserOffice(
FineractEntityAccessType.OFFICE_ACCESS_TO_LOAN_PRODUCTS,
loanproduct.getId());
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_PRODUCT_CREATE,
constructEntityMap(BUSINESS_ENTITY.LOAN_PRODUCT, loanproduct));
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
@@ -332,4 +343,10 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
private void logAsErrorUnexpectedDataIntegrityException(final Exception dve) {
logger.error(dve.getMessage(), dve);
}
private Map<BusinessEventNotificationConstants.BUSINESS_ENTITY, Object> constructEntityMap(final BusinessEventNotificationConstants.BUSINESS_ENTITY entityEvent, Object entity) {
Map<BusinessEventNotificationConstants.BUSINESS_ENTITY, Object> map = new HashMap<>(1);
map.put(entityEvent, entity);
return map;
}
}
@@ -24,11 +24,7 @@ import static org.apache.fineract.portfolio.savings.DepositsApiConstants.recurri
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.transferInterestToSavingsParamName;
import java.math.MathContext;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
import javax.persistence.PersistenceException;
@@ -65,7 +61,9 @@ import org.apache.fineract.portfolio.client.domain.AccountNumberGenerator;
import org.apache.fineract.portfolio.client.domain.Client;
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.apache.fineract.portfolio.common.service.BusinessEventNotifierService;
import org.apache.fineract.portfolio.group.domain.Group;
import org.apache.fineract.portfolio.group.domain.GroupRepository;
import org.apache.fineract.portfolio.group.exception.CenterNotActiveException;
@@ -97,6 +95,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_ENTITY;
import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS;
@Service
public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl implements DepositApplicationProcessWritePlatformService {
@@ -122,6 +122,7 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
private final CalendarInstanceRepository calendarInstanceRepository;
private final ConfigurationDomainService configurationDomainService;
private final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository;
private final BusinessEventNotifierService businessEventNotifierService;
@Autowired
public DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
@@ -136,7 +137,8 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
final RecurringDepositAccountRepository recurringDepositAccountRepository,
final AccountAssociationsRepository accountAssociationsRepository, final FromJsonHelper fromJsonHelper,
final CalendarInstanceRepository calendarInstanceRepository, final ConfigurationDomainService configurationDomainService,
final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository) {
final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository,
final BusinessEventNotifierService businessEventNotifierService) {
this.context = context;
this.savingAccountRepository = savingAccountRepository;
this.depositAccountAssembler = depositAccountAssembler;
@@ -156,6 +158,7 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
this.calendarInstanceRepository = calendarInstanceRepository;
this.configurationDomainService = configurationDomainService;
this.accountNumberFormatRepository = accountNumberFormatRepository;
this.businessEventNotifierService = businessEventNotifierService;
}
/*
@@ -227,6 +230,9 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
final Long savingsId = account.getId();
this.businessEventNotifierService.notifyBusinessEventWasExecuted( BUSINESS_EVENTS.FIXED_DEPOSIT_ACCOUNT_CREATE,
constructEntityMap(BUSINESS_ENTITY.DEPOSIT_ACCOUNT, account));
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withEntityId(savingsId) //
@@ -285,6 +291,9 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
account.validateApplicableInterestRate();
this.savingAccountRepository.save(account);
this.businessEventNotifierService.notifyBusinessEventWasExecuted( BUSINESS_EVENTS.RECURRING_DEPOSIT_ACCOUNT_CREATE,
constructEntityMap(BUSINESS_ENTITY.DEPOSIT_ACCOUNT, account));
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withEntityId(savingsId) //
@@ -769,4 +778,10 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
}
}
}
private Map<BusinessEventNotificationConstants.BUSINESS_ENTITY, Object> constructEntityMap(final BUSINESS_ENTITY entityEvent, Object entity) {
Map<BusinessEventNotificationConstants.BUSINESS_ENTITY, Object> map = new HashMap<>(1);
map.put(entityEvent, entity);
return map;
}
}
@@ -382,6 +382,9 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
}
postInterest(account,postInterestAs,transactionDate);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.SAVINGS_POST_INTEREST,
constructEntityMap(BUSINESS_ENTITY.SAVING, account));
return new CommandProcessingResultBuilder() //
.withEntityId(savingsId) //
.withOfficeId(account.officeId()) //
@@ -682,7 +685,10 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
}
}
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.SAVINGS_CLOSE,
constructEntityMap(BUSINESS_ENTITY.SAVING, account));
// disable all standing orders linked to the savings account
this.disableStandingInstructionsLinkedToClosedSavings(account);
return new CommandProcessingResultBuilder() //
@@ -181,6 +181,9 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
EntityTables.SAVING.getName(), StatusEnum.CREATE.getCode().longValue(),
EntityTables.SAVING.getForeignKeyColumnNameOnDatatable(), account.productId());
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.SAVINGS_CREATE,
constructEntityMap(BUSINESS_ENTITY.SAVING, account));
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withEntityId(savingsId) //
@@ -362,6 +365,9 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
}
}
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.SAVINGS_APPROVE,
constructEntityMap(BUSINESS_ENTITY.SAVING, savingsAccount));
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withEntityId(savingsId) //
@@ -42,8 +42,12 @@ import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityEx
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_ENTITY;
import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS;
import org.apache.fineract.portfolio.accounts.constants.ShareAccountApiConstants;
import org.apache.fineract.portfolio.client.domain.AccountNumberGenerator;
import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants;
import org.apache.fineract.portfolio.common.service.BusinessEventNotifierService;
import org.apache.fineract.portfolio.note.domain.Note;
import org.apache.fineract.portfolio.note.domain.NoteRepository;
import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountTransactionEnumData;
@@ -75,6 +79,8 @@ public class ShareAccountWritePlatformServiceJpaRepositoryImpl implements ShareA
private final JournalEntryWritePlatformService journalEntryWritePlatformService;
private final NoteRepository noteRepository;
private final BusinessEventNotifierService businessEventNotifierService;
@Autowired
public ShareAccountWritePlatformServiceJpaRepositoryImpl(final ShareAccountDataSerializer accountDataSerializer,
@@ -83,14 +89,16 @@ public class ShareAccountWritePlatformServiceJpaRepositoryImpl implements ShareA
final AccountNumberGenerator accountNumberGenerator,
final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository,
final JournalEntryWritePlatformService journalEntryWritePlatformService,
final NoteRepository noteRepository) {
final NoteRepository noteRepository,
final BusinessEventNotifierService businessEventNotifierService) {
this.accountDataSerializer = accountDataSerializer;
this.shareAccountRepository = shareAccountRepository;
this.shareProductRepository = shareProductRepository ;
this.accountNumberGenerator = accountNumberGenerator;
this.accountNumberFormatRepository = accountNumberFormatRepository;
this.journalEntryWritePlatformService = journalEntryWritePlatformService;
this.noteRepository = noteRepository ;
this.noteRepository = noteRepository;
this.businessEventNotifierService = businessEventNotifierService;
}
@Override
@@ -101,6 +109,10 @@ public class ShareAccountWritePlatformServiceJpaRepositoryImpl implements ShareA
generateAccountNumber(account);
journalEntryWritePlatformService.createJournalEntriesForShares(populateJournalEntries(account,
account.getPendingForApprovalSharePurchaseTransactions()));
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.SHARE_ACCOUNT_CREATE,
constructEntityMap(BUSINESS_ENTITY.SHARE_ACCOUNT, account));
return new CommandProcessingResultBuilder() //
.withCommandId(jsonCommand.commandId()) //
.withEntityId(account.getId()) //
@@ -273,6 +285,10 @@ public class ShareAccountWritePlatformServiceJpaRepositoryImpl implements ShareA
this.shareProductRepository.save(shareProduct);
this.journalEntryWritePlatformService.createJournalEntriesForShares(populateJournalEntries(account, journalTransactions));
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.SHARE_ACCOUNT_APPROVE,
constructEntityMap(BUSINESS_ENTITY.SHARE_ACCOUNT, account));
return new CommandProcessingResultBuilder() //
.withCommandId(jsonCommand.commandId()) //
.withEntityId(accountId) //
@@ -516,4 +532,10 @@ public class ShareAccountWritePlatformServiceJpaRepositoryImpl implements ShareA
throw new PlatformDataIntegrityException("error.msg.shareaccount.unknown.data.integrity.issue",
"Unknown data integrity issue with resource.");
}
private Map<BusinessEventNotificationConstants.BUSINESS_ENTITY, Object> constructEntityMap(final BusinessEventNotificationConstants.BUSINESS_ENTITY entityEvent, Object entity) {
Map<BusinessEventNotificationConstants.BUSINESS_ENTITY, Object> map = new HashMap<>(1);
map.put(entityEvent, entity);
return map;
}
}
@@ -19,6 +19,7 @@
package org.apache.fineract.portfolio.shareproducts.service;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
import javax.persistence.PersistenceException;
@@ -30,6 +31,10 @@ import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_ENTITY;
import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS;
import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants;
import org.apache.fineract.portfolio.common.service.BusinessEventNotifierService;
import org.apache.fineract.portfolio.shareproducts.constants.ShareProductApiConstants;
import org.apache.fineract.portfolio.shareproducts.domain.ShareProduct;
import org.apache.fineract.portfolio.shareproducts.domain.ShareProductDividendPayOutDetails;
@@ -53,19 +58,22 @@ public class ShareProductWritePlatformServiceJpaRepositoryImpl implements ShareP
private final ShareProductDividentPayOutDetailsRepositoryWrapper shareProductDividentPayOutDetailsRepository;
private final ShareProductDividendAssembler shareProductDividendAssembler;
private final ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService;
private final BusinessEventNotifierService businessEventNotifierService;
@Autowired
public ShareProductWritePlatformServiceJpaRepositoryImpl(final ShareProductRepositoryWrapper repository,
final ShareProductDataSerializer serializer, final FromJsonHelper fromApiJsonHelper,
final ShareProductDividentPayOutDetailsRepositoryWrapper shareProductDividentPayOutDetailsRepositor,
final ShareProductDividendAssembler shareProductDividendAssembler,
final ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService) {
final ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService,
final BusinessEventNotifierService businessEventNotifierService) {
this.repository = repository;
this.serializer = serializer;
this.fromApiJsonHelper = fromApiJsonHelper;
this.shareProductDividentPayOutDetailsRepository = shareProductDividentPayOutDetailsRepositor;
this.shareProductDividendAssembler = shareProductDividendAssembler;
this.accountMappingWritePlatformService = accountMappingWritePlatformService;
this.businessEventNotifierService = businessEventNotifierService;
}
@Override
@@ -140,6 +148,10 @@ public class ShareProductWritePlatformServiceJpaRepositoryImpl implements ShareP
"No eligible shares for creating dividends"); }
this.shareProductDividentPayOutDetailsRepository.save(dividendPayOutDetails);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.SHARE_PRODUCT_DIVIDENDS_CREATE,
constructEntityMap(BUSINESS_ENTITY.SHARE_PRODUCT, productId));
return new CommandProcessingResultBuilder() //
.withCommandId(jsonCommand.commandId()) //
.withEntityId(productId) //
@@ -204,4 +216,10 @@ public class ShareProductWritePlatformServiceJpaRepositoryImpl implements ShareP
"Unknown data integrity issue with resource.");
}
private Map<BusinessEventNotificationConstants.BUSINESS_ENTITY, Object> constructEntityMap(final BusinessEventNotificationConstants.BUSINESS_ENTITY entityEvent, Object entity) {
Map<BusinessEventNotificationConstants.BUSINESS_ENTITY, Object> map = new HashMap<>(1);
map.put(entityEvent, entity);
return map;
}
}
@@ -52,6 +52,7 @@
org.apache.fineract.portfolio.*,
org.apache.fineract.useradministration.*,
org.apache.fineract.mix.*,
org.apache.fineract.notification.*,
org.apache.fineract.template.*,
org.apache.fineract.template.service.*,
org.apache.fineract.useradministration.*,
@@ -82,6 +83,7 @@
<jpa:repositories base-package="org.apache.fineract.template.domain" />
<jpa:repositories base-package="org.apache.fineract.infrastructure.campaigns.sms.domain" />
<jpa:repositories base-package="org.apache.fineract.adhocquery.domain" />
<jpa:repositories base-package="org.apache.fineract.notification.domain"/>
<import resource="infrastructure.xml" />
@@ -96,4 +98,6 @@
</bean>
<import resource="spmContext.xml"/>
<import resource="notificationContext.xml"/>
</beans>
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="amqConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<constructor-arg index="0" value="tcp://localhost:61616" />
</bean>
<bean id="connectionFactory" class="org.springframework.jms.connection.CachingConnectionFactory">
<constructor-arg ref="amqConnectionFactory"/>
</bean>
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="connectionFactory"/>
</bean>
<bean id="messageListenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory" />
<property name="destinationName" value="NotificationQueue"/>
<property name="messageListener" ref="notificationEventListener" />
</bean>
</beans>
@@ -0,0 +1,48 @@
--
-- Licensed to the Apache Software Foundation (ASF) under one
-- or more contributor license agreements. See the NOTICE file
-- distributed with this work for additional information
-- regarding copyright ownership. The ASF licenses this file
-- to you under the Apache License, Version 2.0 (the
-- "License"); you may not use this file except in compliance
-- with the License. You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing,
-- software distributed under the License is distributed on an
-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-- KIND, either express or implied. See the License for the
-- specific language governing permissions and limitations
-- under the License.
--
CREATE TABLE IF NOT EXISTS `notification_generator` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`object_type` text,
`object_identifier` bigint(20) DEFAULT NULL,
`action` text,
`actor` bigint(20),
`is_system_generated` tinyint(1) DEFAULT '0',
`notification_content` text,
`created_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=15 ;
CREATE TABLE IF NOT EXISTS `notification_mapper` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`notification_id` bigint(20) DEFAULT NULL,
`user_id` bigint(20) DEFAULT NULL,
`is_read` tinyint(1) DEFAULT '0',
`created_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `notification_mapper` (`user_id`),
KEY `notification_id` (`notification_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=15 ;
ALTER TABLE `notification_mapper`
ADD CONSTRAINT `notification_mapper_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `m_appuser` (`id`),
ADD CONSTRAINT `notification_mapper_ibfk_3` FOREIGN KEY (`notification_id`) REFERENCES `notification_generator` (`id`);
@@ -0,0 +1,33 @@
package org.apache.fineract.notification;
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import org.springframework.jms.listener.SessionAwareMessageListener;
import javax.jms.*;
public class Listener implements SessionAwareMessageListener {
@Override
public void onMessage(Message message, Session session) throws JMSException {
TextMessage msg = (TextMessage) message;
System.out.println("Received: " + msg.getText());
}
}
@@ -0,0 +1,52 @@
package org.apache.fineract.notification;
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;
import javax.jms.JMSException;
import javax.jms.Session;
import javax.jms.TextMessage;
@RunWith(MockitoJUnitRunner.class)
public class ListenerTest {
private Listener listener;
private Session session;
private TextMessage textMessageMock;
@Before
public void setUp() {
listener = new Listener();
session = Mockito.mock(Session.class);
textMessageMock = Mockito.mock(TextMessage.class);
}
@Test
public void testListener() throws JMSException {
Mockito.when(textMessageMock.getText()).thenReturn("content");
listener.onMessage(textMessageMock, session);
Mockito.verify(textMessageMock).getText();
}
}
@@ -0,0 +1,77 @@
package org.apache.fineract.notification;
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import com.mockrunner.mock.jms.MockQueue;
import org.apache.fineract.notification.data.NotificationData;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
@ContextConfiguration(locations = {
"classpath:META-INF/testNotificationContext.xml",
})
@RunWith(SpringJUnit4ClassRunner.class)
public class SenderTest {
@Autowired
private JmsTemplate jmsTemplate;
@Autowired
private MockQueue mockQueue;
@Test
public void notificationCreation() {
String objectType = "CLIENT";
Long objectIdentifier = 1L;
String action = "created";
Long actorId = 1L;
String notificationContent = "A client was created";
NotificationData notificationData = new NotificationData(
objectType,
objectIdentifier,
action,
actorId,
notificationContent,
false,
null,
null,
null
);
jmsTemplate.send(mockQueue, new MessageCreator() {
@Override
public Message createMessage(Session session) throws JMSException {
System.out.println("Message send successfully");
return session.createObjectMessage(notificationData);
}
});
}
}
@@ -0,0 +1,130 @@
package org.apache.fineract.notification;
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import org.apache.fineract.notification.domain.Notification;
import org.apache.fineract.notification.domain.NotificationMapper;
import org.apache.fineract.notification.service.NotificationGeneratorReadRepositoryWrapper;
import org.apache.fineract.notification.service.NotificationGeneratorWritePlatformService;
import org.apache.fineract.notification.service.NotificationMapperWritePlatformService;
import org.apache.fineract.notification.service.NotificationWritePlatformServiceImpl;
import org.apache.fineract.useradministration.domain.AppUser;
import org.apache.fineract.useradministration.domain.AppUserRepository;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import java.text.SimpleDateFormat;
import java.util.Date;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class StorageTest {
private NotificationWritePlatformServiceImpl notificationWritePlatformServiceImpl;
@Mock
private NotificationGeneratorReadRepositoryWrapper notificationGeneratorReadRepositoryWrapper;
@Mock
private NotificationGeneratorWritePlatformService notificationGeneratorWritePlatformService;
@Mock
private NotificationMapperWritePlatformService notificationMapperWritePlatformService;
@Mock
private AppUserRepository appUserRepository;
@Before
public void setUp() {
notificationWritePlatformServiceImpl = new NotificationWritePlatformServiceImpl(
notificationGeneratorWritePlatformService,
notificationGeneratorReadRepositoryWrapper,
appUserRepository,
notificationMapperWritePlatformService);
}
@Test
public void testNotificationStorage() {
Long userId = 1L;
String objectType = "CLIENT";
Long objectIdentifier = 1L;
String action = "created";
Long actor = 1L;
String notificationContent = "A client was created";
boolean isSystemGenerated = false;
Notification notification = new Notification(
objectType,
objectIdentifier,
action,
actor,
isSystemGenerated,
notificationContent,
getCurrentDateTime()
);
AppUser appUser = this.appUserRepository.findOne(1L);
NotificationMapper notificationMapper = new NotificationMapper(
notification,
appUser,
false,
getCurrentDateTime()
);
when(this.notificationGeneratorWritePlatformService.create(refEq(notification))).thenReturn(1L);
when(this.appUserRepository.findOne(userId)).thenReturn(appUser);
when(this.notificationGeneratorReadRepositoryWrapper.findById(1L)).thenReturn(notification);
when(this.notificationMapperWritePlatformService.create(refEq(notificationMapper))).thenReturn(1L);
Long actualGeneratedNotificationId =
notificationWritePlatformServiceImpl.notify(
userId,
objectType,
objectIdentifier,
action,
actor,
notificationContent,
isSystemGenerated
);
verify(this.notificationGeneratorWritePlatformService, times(1)).create(refEq(notification));
verify(this.notificationMapperWritePlatformService, times(1)).create(refEq(notificationMapper));
verify(this.notificationGeneratorReadRepositoryWrapper, times(1)).findById(1L);
assertEquals(actualGeneratedNotificationId, new Long(1));
}
private String getCurrentDateTime() {
Date date = new Date();
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return formatter.format(date);
}
}
@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jms="http://www.springframework.org/schema/jms"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms-2.5.xsd">
<bean id="destinationManager" class="com.mockrunner.jms.DestinationManager"/>
<bean id="mockQueue" factory-bean="destinationManager" factory-method="createQueue">
<constructor-arg index="0" value="demoMockRunnerQueue" />
</bean>
<bean id="configurationManager" class="com.mockrunner.jms.ConfigurationManager"/>
<bean id="jmsQueueConnectionFactory" class="com.mockrunner.mock.jms.MockQueueConnectionFactory">
<constructor-arg index="0" ref="destinationManager" />
<constructor-arg index="1" ref="configurationManager" />
</bean>
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="jmsQueueConnectionFactory" />
</bean>
<jms:listener-container connection-factory="jmsQueueConnectionFactory" >
<jms:listener destination="demoMockRunnerQueue" ref="testListener" method="onMessage" />
</jms:listener-container>
<bean id="testListener" class="org.apache.fineract.notification.Listener" />
</beans>
+26
View File
@@ -0,0 +1,26 @@
Copyright 1992-2017 The FreeBSD Project. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list
of conditions and the following disclaimer.Redistributions in binary form must
reproduce the above copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
METASTUFF, LTD. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE.
The views and conclusions contained in the software and documentation are those
of the authors and should not be interpreted as representing official policies,
either expressed or implied, of the FreeBSD Project.