diff --git a/custom/v3/command/core/build.gradle b/custom/v3/command/core/build.gradle new file mode 100644 index 00000000000..21e3f60d259 --- /dev/null +++ b/custom/v3/command/core/build.gradle @@ -0,0 +1,26 @@ +/** + * 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. + */ + +description = 'The new generation of the API layer (v3): Command Core' + +group = 'org.apache.fineract.v3.command' + +archivesBaseName = 'fineract-custom-command-processing-core' + +apply from: 'dependencies.gradle' diff --git a/custom/v3/command/core/dependencies.gradle b/custom/v3/command/core/dependencies.gradle new file mode 100644 index 00000000000..016fd687f67 --- /dev/null +++ b/custom/v3/command/core/dependencies.gradle @@ -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. + */ + +dependencies { + implementation(project(':fineract-core')) + implementation('org.springframework.boot:spring-boot-starter') + compileOnly('org.eclipse.persistence:org.eclipse.persistence.jpa') +} diff --git a/custom/v3/command/core/src/main/java/org/apache/fineract/v3/command/core/CommandProperties.java b/custom/v3/command/core/src/main/java/org/apache/fineract/v3/command/core/CommandProperties.java new file mode 100644 index 00000000000..72ea352b46e --- /dev/null +++ b/custom/v3/command/core/src/main/java/org/apache/fineract/v3/command/core/CommandProperties.java @@ -0,0 +1,32 @@ +/** + * 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.v3.command.core; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@Getter +@Setter +@ConfigurationProperties(prefix = "fineract.command") +public class CommandProperties { + + private Boolean enabled = true; +} diff --git a/custom/v3/command/data/build.gradle b/custom/v3/command/data/build.gradle new file mode 100644 index 00000000000..122700df03b --- /dev/null +++ b/custom/v3/command/data/build.gradle @@ -0,0 +1,25 @@ +/** + * 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. + */ +description = 'The new generation of the API layer (v3): Command Data' + +group = 'org.apache.fineract.v3.command' + +archivesBaseName = 'fineract-custom-command-data' + +apply from: 'dependencies.gradle' diff --git a/custom/v3/command/data/dependencies.gradle b/custom/v3/command/data/dependencies.gradle new file mode 100644 index 00000000000..10b4c618d8d --- /dev/null +++ b/custom/v3/command/data/dependencies.gradle @@ -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. + */ + +dependencies { + implementation(project(':fineract-core')) + implementation('org.springframework.boot:spring-boot-starter') + implementation('com.fasterxml.jackson.core:jackson-databind') +} diff --git a/custom/v3/command/data/src/main/java/org/apache/fineract/v3/command/data/CommandRequest.java b/custom/v3/command/data/src/main/java/org/apache/fineract/v3/command/data/CommandRequest.java new file mode 100644 index 00000000000..bd9cdc0b819 --- /dev/null +++ b/custom/v3/command/data/src/main/java/org/apache/fineract/v3/command/data/CommandRequest.java @@ -0,0 +1,46 @@ +/** + * 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.v3.command.data; + +import java.io.Serial; +import java.io.Serializable; +import java.time.OffsetDateTime; +import java.util.UUID; +import lombok.Data; +import lombok.experimental.FieldNameConstants; +import lombok.experimental.SuperBuilder; +import org.apache.fineract.commands.domain.CommandProcessingResultType; + +@Data +@SuperBuilder +@FieldNameConstants +public class CommandRequest implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private UUID requestIdempotencyKey; + private CommandProcessingResultType commandProcessingStatus; + private String commandResult; + private Integer resultStatusCode; + private OffsetDateTime createdDate; + private OffsetDateTime processedAt; + private T requestBody; +} diff --git a/custom/v3/command/data/src/main/java/org/apache/fineract/v3/command/data/CommandResponse.java b/custom/v3/command/data/src/main/java/org/apache/fineract/v3/command/data/CommandResponse.java new file mode 100644 index 00000000000..8427fe8ee94 --- /dev/null +++ b/custom/v3/command/data/src/main/java/org/apache/fineract/v3/command/data/CommandResponse.java @@ -0,0 +1,46 @@ +/** + * 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.v3.command.data; + +import com.fasterxml.jackson.annotation.JsonUnwrapped; +import java.io.Serial; +import java.io.Serializable; +import java.time.OffsetDateTime; +import lombok.Data; +import lombok.experimental.FieldNameConstants; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@FieldNameConstants +public class CommandResponse implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private String tenantId; + private Long userId; + private String errorCode; + private String errorMessage; + private OffsetDateTime createdAt; + + @JsonUnwrapped + private T data; +} diff --git a/custom/v3/command/domain/build.gradle b/custom/v3/command/domain/build.gradle new file mode 100644 index 00000000000..9333941b67d --- /dev/null +++ b/custom/v3/command/domain/build.gradle @@ -0,0 +1,25 @@ +/** + * 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. + */ +description = 'The new generation of the API layer (v3): Command Domain' + +group = 'org.apache.fineract.v3.command' + +archivesBaseName = 'fineract-custom-command-domain' + +apply from: 'dependencies.gradle' diff --git a/custom/v3/command/domain/dependencies.gradle b/custom/v3/command/domain/dependencies.gradle new file mode 100644 index 00000000000..f8a40f2ee7d --- /dev/null +++ b/custom/v3/command/domain/dependencies.gradle @@ -0,0 +1,36 @@ +/** + * 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. + */ + +dependencies { + + implementation(project(':fineract-core')) + + implementation('org.springframework.boot:spring-boot-starter-data-jpa') { + exclude group: 'org.hibernate' + } + implementation('org.eclipse.persistence:org.eclipse.persistence.jpa') { + exclude group: 'org.eclipse.persistence', module: 'jakarta.persistence' + } + + implementation( + 'org.springframework.boot:spring-boot-starter', + 'org.springframework.boot:spring-boot-starter-data-jdbc', + 'com.fasterxml.jackson.core:jackson-databind', + ) +} diff --git a/custom/v3/command/domain/src/main/java/org/apache/fineract/v3/command/domain/Command.java b/custom/v3/command/domain/src/main/java/org/apache/fineract/v3/command/domain/Command.java new file mode 100644 index 00000000000..b5108178c00 --- /dev/null +++ b/custom/v3/command/domain/src/main/java/org/apache/fineract/v3/command/domain/Command.java @@ -0,0 +1,73 @@ +/** + * 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.v3.command.domain; + +import com.fasterxml.jackson.databind.JsonNode; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Table; +import java.time.OffsetDateTime; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.Accessors; +import lombok.experimental.FieldNameConstants; +import org.apache.fineract.commands.domain.CommandProcessingResultType; +import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; + +@Entity +@Getter +@Setter +@FieldNameConstants +@Accessors(chain = true) +@AllArgsConstructor +@NoArgsConstructor +@Table(name = "m_command") +public class Command extends AbstractPersistableCustom { + + @Column(name = "request_idempotency_key", nullable = false, unique = true) + private UUID requestIdempotencyKey; + + @Enumerated(EnumType.STRING) + @Column(name = "command_processing_status") + private CommandProcessingResultType commandProcessingStatus; + + @Column(name = "command_result") + private String commandResult; + + @Column(name = "result_status_code") + private Integer resultStatusCode; + + @Column(name = "created_date") + private OffsetDateTime createdDate; + + @Column(name = "processed_at") + private OffsetDateTime processedAt; + + @Column(name = "request_body") + @Convert(converter = JsonNodeConverter.class) + private JsonNode requestBody; + +} diff --git a/custom/v3/command/domain/src/main/java/org/apache/fineract/v3/command/domain/CommandRepository.java b/custom/v3/command/domain/src/main/java/org/apache/fineract/v3/command/domain/CommandRepository.java new file mode 100644 index 00000000000..911e5226841 --- /dev/null +++ b/custom/v3/command/domain/src/main/java/org/apache/fineract/v3/command/domain/CommandRepository.java @@ -0,0 +1,25 @@ +/** + * 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.v3.command.domain; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +public interface CommandRepository extends JpaRepository, JpaSpecificationExecutor {} diff --git a/custom/v3/command/domain/src/main/java/org/apache/fineract/v3/command/domain/JsonNodeConverter.java b/custom/v3/command/domain/src/main/java/org/apache/fineract/v3/command/domain/JsonNodeConverter.java new file mode 100644 index 00000000000..22e2c544e29 --- /dev/null +++ b/custom/v3/command/domain/src/main/java/org/apache/fineract/v3/command/domain/JsonNodeConverter.java @@ -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.v3.command.domain; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; + +@Converter(autoApply = true) +public class JsonNodeConverter implements AttributeConverter { + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + @Override + public String convertToDatabaseColumn(JsonNode attribute) { + try { + return objectMapper.writeValueAsString(attribute); + } catch (JsonProcessingException e) { + throw new IllegalArgumentException("Error converting JsonNode to String", e); + } + } + + @Override + public JsonNode convertToEntityAttribute(String dbData) { + try { + return objectMapper.readTree(dbData); + } catch (JsonProcessingException e) { + throw new IllegalArgumentException("Error converting String to JsonNode", e); + } + } +} diff --git a/custom/v3/command/domain/src/main/resources/db/custom-changelog/0001_create_command_table.xml b/custom/v3/command/domain/src/main/resources/db/custom-changelog/0001_create_command_table.xml new file mode 100644 index 00000000000..0e1ace486f9 --- /dev/null +++ b/custom/v3/command/domain/src/main/resources/db/custom-changelog/0001_create_command_table.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/custom/v3/command/domain/src/main/resources/jpa/command/persistence.xml b/custom/v3/command/domain/src/main/resources/jpa/command/persistence.xml new file mode 100644 index 00000000000..5d9e056b1d6 --- /dev/null +++ b/custom/v3/command/domain/src/main/resources/jpa/command/persistence.xml @@ -0,0 +1,39 @@ + + + + + + + + + org.eclipse.persistence.jpa.PersistenceProvider + + org.apache.fineract.v3.command.domain.Command + + false + + + + + diff --git a/custom/v3/command/mapping/build.gradle b/custom/v3/command/mapping/build.gradle new file mode 100644 index 00000000000..507d7fb8cfc --- /dev/null +++ b/custom/v3/command/mapping/build.gradle @@ -0,0 +1,26 @@ +/** + * 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. + */ + +description = 'The new generation of the API layer (v3): Command Mapper' + +group = 'org.apache.fineract.v3.command' + +archivesBaseName = 'fineract-custom-command-mapping' + +apply from: 'dependencies.gradle' diff --git a/custom/v3/command/mapping/dependencies.gradle b/custom/v3/command/mapping/dependencies.gradle new file mode 100644 index 00000000000..09d9de145d4 --- /dev/null +++ b/custom/v3/command/mapping/dependencies.gradle @@ -0,0 +1,39 @@ +/** + * 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. + */ + +dependencies { + implementation( + project(':fineract-core'), + project(':custom:v3:command:data'), + project(':custom:v3:command:domain'), + 'org.springframework.boot:spring-boot-starter', + 'com.fasterxml.jackson.core:jackson-databind', + 'org.mapstruct:mapstruct', + ) + + implementation('org.springframework.boot:spring-boot-starter-data-jpa') { + exclude group: 'org.hibernate' + } + implementation('org.eclipse.persistence:org.eclipse.persistence.jpa') { + exclude group: 'org.eclipse.persistence', module: 'jakarta.persistence' + } + + compileOnly(project(':fineract-provider')) + annotationProcessor('org.mapstruct:mapstruct-processor') +} diff --git a/custom/v3/command/mapping/src/main/java/org/apache/fineract/v3/command/mapping/CommandMapper.java b/custom/v3/command/mapping/src/main/java/org/apache/fineract/v3/command/mapping/CommandMapper.java new file mode 100644 index 00000000000..0ca31179476 --- /dev/null +++ b/custom/v3/command/mapping/src/main/java/org/apache/fineract/v3/command/mapping/CommandMapper.java @@ -0,0 +1,52 @@ +/** + * 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.v3.command.mapping; + +import static org.mapstruct.NullValueCheckStrategy.ALWAYS; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig; +import org.apache.fineract.v3.command.data.CommandRequest; +import org.apache.fineract.v3.command.domain.Command; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; + +@Mapper(config = MapstructMapperConfig.class, nullValueCheckStrategy = ALWAYS) +public interface CommandMapper { + + @Mapping(target = "id", ignore = true) + @Mapping(target = "requestBody", source = "requestBody", qualifiedByName = "mapRequestBody") + Command map(CommandRequest source); + + @Named("mapRequestBody") + default JsonNode mapRequestBody(Object body) { + if (body instanceof JsonNode) { + return (JsonNode) body; + } + try { + ObjectMapper objectMapper = new ObjectMapper(); + return objectMapper.valueToTree(body); + } catch (Exception e) { + throw new IllegalArgumentException("Error mapping body to JsonNode: " + body.getClass().getName(), e); + } + } +} diff --git a/custom/v3/command/service/build.gradle b/custom/v3/command/service/build.gradle new file mode 100644 index 00000000000..4612d660ce9 --- /dev/null +++ b/custom/v3/command/service/build.gradle @@ -0,0 +1,26 @@ +/** + * 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. + */ + +description = 'The new generation of the API layer (v3): Command Services' + +group = 'org.apache.fineract.v3.command' + +archivesBaseName = 'fineract-custom-command-service' + +apply from: 'dependencies.gradle' diff --git a/custom/v3/command/service/dependencies.gradle b/custom/v3/command/service/dependencies.gradle new file mode 100644 index 00000000000..f619b5d57a4 --- /dev/null +++ b/custom/v3/command/service/dependencies.gradle @@ -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. + */ + +dependencies { + implementation(project(':fineract-core')) + implementation(project(':custom:v3:command:data')) + implementation(project(':custom:v3:command:domain')) + implementation(project(':custom:v3:command:mapping')) + implementation('org.apache.httpcomponents:httpcore') + implementation('jakarta.servlet:jakarta.servlet-api') + implementation('org.springframework.data:spring-data-commons') + + implementation('org.springframework.boot:spring-boot-starter-data-jpa') { + exclude group: 'org.hibernate' + } + implementation('org.eclipse.persistence:org.eclipse.persistence.jpa') { + exclude group: 'org.eclipse.persistence', module: 'jakarta.persistence' + } +} diff --git a/custom/v3/command/service/src/main/java/org/apache/fineract/v3/command/service/CommandDispatcher.java b/custom/v3/command/service/src/main/java/org/apache/fineract/v3/command/service/CommandDispatcher.java new file mode 100644 index 00000000000..3ed2f6147c2 --- /dev/null +++ b/custom/v3/command/service/src/main/java/org/apache/fineract/v3/command/service/CommandDispatcher.java @@ -0,0 +1,28 @@ +/** + * 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.v3.command.service; + +import org.apache.fineract.v3.command.data.CommandRequest; +import org.apache.fineract.v3.command.data.CommandResponse; + +public interface CommandDispatcher { + + CommandResponse dispatch(CommandRequest request); +} diff --git a/custom/v3/command/service/src/main/java/org/apache/fineract/v3/command/service/CommandFilter.java b/custom/v3/command/service/src/main/java/org/apache/fineract/v3/command/service/CommandFilter.java new file mode 100644 index 00000000000..9f8e68bec88 --- /dev/null +++ b/custom/v3/command/service/src/main/java/org/apache/fineract/v3/command/service/CommandFilter.java @@ -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.v3.command.service; + +import org.apache.fineract.v3.command.data.CommandRequest; + +public interface CommandFilter { + + boolean filter(CommandRequest request); +} diff --git a/custom/v3/command/service/src/main/java/org/apache/fineract/v3/command/service/CommandHandler.java b/custom/v3/command/service/src/main/java/org/apache/fineract/v3/command/service/CommandHandler.java new file mode 100644 index 00000000000..3ff83658dc7 --- /dev/null +++ b/custom/v3/command/service/src/main/java/org/apache/fineract/v3/command/service/CommandHandler.java @@ -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.v3.command.service; + +import org.apache.fineract.v3.command.data.CommandRequest; +import org.apache.fineract.v3.command.data.CommandResponse; + +public interface CommandHandler, O extends CommandResponse> { + + Class getRequestType(); + + O handle(I request); + + default boolean canHandle(CommandRequest request) { + return getRequestType().isInstance(request); + } +} diff --git a/custom/v3/command/service/src/main/java/org/apache/fineract/v3/command/service/CommandProcessor.java b/custom/v3/command/service/src/main/java/org/apache/fineract/v3/command/service/CommandProcessor.java new file mode 100644 index 00000000000..7123fa5f106 --- /dev/null +++ b/custom/v3/command/service/src/main/java/org/apache/fineract/v3/command/service/CommandProcessor.java @@ -0,0 +1,28 @@ +/** + * 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.v3.command.service; + +import org.apache.fineract.v3.command.data.CommandRequest; +import org.apache.fineract.v3.command.data.CommandResponse; + +public interface CommandProcessor { + + CommandResponse process(CommandRequest request); +} diff --git a/custom/v3/command/service/src/main/java/org/apache/fineract/v3/command/service/DefaultCommandDispatcher.java b/custom/v3/command/service/src/main/java/org/apache/fineract/v3/command/service/DefaultCommandDispatcher.java new file mode 100644 index 00000000000..dc788d4a760 --- /dev/null +++ b/custom/v3/command/service/src/main/java/org/apache/fineract/v3/command/service/DefaultCommandDispatcher.java @@ -0,0 +1,55 @@ +/** + * 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.v3.command.service; + +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.v3.command.data.CommandRequest; +import org.apache.fineract.v3.command.data.CommandResponse; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; + +@Slf4j +@RequiredArgsConstructor +@Service +public class DefaultCommandDispatcher implements CommandDispatcher { + + private final CommandProcessor defaultCommandProcessor; + private final CommandFilter idempotentFilter; + private final ApplicationEventPublisher publisher; + + @Override + public CommandResponse dispatch(CommandRequest request) { + + if (!idempotentFilter.filter(request)) { + // TODO: throw proper platform exception + throw new RuntimeException("Duplicate request: " + request.getRequestIdempotencyKey()); + } + + // TODO: eventually add more processors/filters if needed until we match the current implementation + request.setRequestIdempotencyKey(UUID.randomUUID()); + + // TODO: create event object and do not send the entire request + publisher.publishEvent(request); + + return defaultCommandProcessor.process(request); + } +} diff --git a/custom/v3/command/service/src/main/java/org/apache/fineract/v3/command/service/DefaultCommandIdempotentFilter.java b/custom/v3/command/service/src/main/java/org/apache/fineract/v3/command/service/DefaultCommandIdempotentFilter.java new file mode 100644 index 00000000000..b08dda4e4b9 --- /dev/null +++ b/custom/v3/command/service/src/main/java/org/apache/fineract/v3/command/service/DefaultCommandIdempotentFilter.java @@ -0,0 +1,47 @@ +/** + * 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.v3.command.service; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.v3.command.data.CommandRequest; +import org.springframework.stereotype.Component; + +@Slf4j +@RequiredArgsConstructor +@Component +public class DefaultCommandIdempotentFilter implements CommandFilter { + + // TODO: more sophisticated implementation, possibly with distributed SET/MAP (Redis) + private static final Set REQUEST_IDS = new HashSet<>(); + + @Override + public boolean filter(CommandRequest request) { + + boolean pass = !REQUEST_IDS.contains(request.getRequestIdempotencyKey()); + + REQUEST_IDS.add(request.getRequestIdempotencyKey()); + + return pass; + } +} diff --git a/custom/v3/command/service/src/main/java/org/apache/fineract/v3/command/service/DefaultCommandProcessor.java b/custom/v3/command/service/src/main/java/org/apache/fineract/v3/command/service/DefaultCommandProcessor.java new file mode 100644 index 00000000000..b0f56952368 --- /dev/null +++ b/custom/v3/command/service/src/main/java/org/apache/fineract/v3/command/service/DefaultCommandProcessor.java @@ -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.v3.command.service; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.commands.exception.UnsupportedCommandException; +import org.apache.fineract.v3.command.data.CommandRequest; +import org.apache.fineract.v3.command.data.CommandResponse; +import org.apache.fineract.v3.command.domain.CommandRepository; +import org.apache.fineract.v3.command.mapping.CommandMapper; +import org.springframework.stereotype.Service; + +@Slf4j +@RequiredArgsConstructor +@Service +public class DefaultCommandProcessor implements CommandProcessor { + + private final List> handlers; + + private final CommandRepository commandRepository; + + private final CommandMapper commandMapper; + + @Override + public CommandResponse process(CommandRequest request) { + + commandRepository.saveAndFlush(commandMapper.map(request)); + + return handlers.stream().filter(handler -> handler.canHandle(request)) // + .findFirst() // + .map(handler -> ((CommandHandler, CommandResponse>) handler).handle(request)) // + .orElseThrow(() -> new UnsupportedCommandException("No command handler for: " + request.getClass().getCanonicalName())); + } +} diff --git a/custom/v3/command/starter/build.gradle b/custom/v3/command/starter/build.gradle new file mode 100644 index 00000000000..2b410fb2778 --- /dev/null +++ b/custom/v3/command/starter/build.gradle @@ -0,0 +1,26 @@ +/** + * 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. + */ + +description = 'The new generation of the API layer (v3): Command Starter' + +group = 'org.apache.fineract.v3.command' + +archivesBaseName = 'fineract-custom-command-starter' + +apply from: 'dependencies.gradle' diff --git a/custom/v3/command/starter/dependencies.gradle b/custom/v3/command/starter/dependencies.gradle new file mode 100644 index 00000000000..bb22a063006 --- /dev/null +++ b/custom/v3/command/starter/dependencies.gradle @@ -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. + */ + +dependencies { + implementation( + project(':custom:v3:command:core'), + project(':custom:v3:command:data'), + project(':custom:v3:command:service'), + 'org.springframework.boot:spring-boot-starter', + ) +} diff --git a/custom/v3/command/starter/src/main/java/org/apache/fineract/v3/command/starter/CommandAutoConfiguration.java b/custom/v3/command/starter/src/main/java/org/apache/fineract/v3/command/starter/CommandAutoConfiguration.java new file mode 100644 index 00000000000..4fd1f5de2e7 --- /dev/null +++ b/custom/v3/command/starter/src/main/java/org/apache/fineract/v3/command/starter/CommandAutoConfiguration.java @@ -0,0 +1,32 @@ +/** + * 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.v3.command.starter; + +import org.apache.fineract.v3.command.core.CommandProperties; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.ComponentScan; + +@AutoConfiguration +@EnableConfigurationProperties({ CommandProperties.class }) +@ComponentScan("org.apache.fineract.v3.command") +@ConditionalOnProperty(value = "fineract.command.enabled", havingValue = "true") +public class CommandAutoConfiguration {} diff --git a/custom/v3/command/starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/custom/v3/command/starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 00000000000..7e7f9a02c7c --- /dev/null +++ b/custom/v3/command/starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +org.apache.fineract.v3.command.starter.CommandAutoConfiguration diff --git a/custom/v3/common/configuration/src/main/java/org/apache/fineract/v3/common/configuration/CommonWebConfiguration.java b/custom/v3/common/configuration/src/main/java/org/apache/fineract/v3/common/configuration/CommonWebConfiguration.java index 54a5a11b80b..840b84b5683 100644 --- a/custom/v3/common/configuration/src/main/java/org/apache/fineract/v3/common/configuration/CommonWebConfiguration.java +++ b/custom/v3/common/configuration/src/main/java/org/apache/fineract/v3/common/configuration/CommonWebConfiguration.java @@ -44,7 +44,9 @@ import org.springframework.core.annotation.Order; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.access.ExceptionTranslationFilter; @@ -53,6 +55,7 @@ @Slf4j @Configuration +@EnableMethodSecurity @RequiredArgsConstructor public class CommonWebConfiguration { @@ -85,10 +88,11 @@ public class CommonWebConfiguration { @Bean @Order(1) public SecurityFilterChain v3FilterChain(HttpSecurity http) throws Exception { - http.securityMatcher(antMatcher("/v3/**")).authorizeHttpRequests((auth) -> { - auth.requestMatchers(antMatcher(HttpMethod.OPTIONS, "/v3/**")).permitAll().requestMatchers(antMatcher("/v3/**")) - .fullyAuthenticated(); - }).httpBasic((httpBasic) -> httpBasic.authenticationEntryPoint(basicAuthenticationEntryPoint)) + + http.securityMatcher(antMatcher("/v3/**")).csrf(AbstractHttpConfigurer::disable) + .authorizeHttpRequests((auth) -> auth.requestMatchers(antMatcher(HttpMethod.OPTIONS, "/v3/**")).permitAll() + .requestMatchers(antMatcher("/v3/**")).fullyAuthenticated()) + .httpBasic((httpBasic) -> httpBasic.authenticationEntryPoint(basicAuthenticationEntryPoint)) .sessionManagement((smc) -> smc.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .addFilterBefore(tenantAwareBasicAuthenticationFilter(), SecurityContextHolderFilter.class) .addFilterAfter(requestResponseFilter(), ExceptionTranslationFilter.class) diff --git a/custom/v3/common/configuration/src/main/resources/application-v3.properties b/custom/v3/common/configuration/src/main/resources/application-v3.properties index 80b148301de..4ec822e1505 100644 --- a/custom/v3/common/configuration/src/main/resources/application-v3.properties +++ b/custom/v3/common/configuration/src/main/resources/application-v3.properties @@ -19,5 +19,11 @@ debug=true -# note +# Note fineract.note.enabled=true + +# Command +fineract.command.enabled=true + +# Include only non-values in the response +spring.jackson.default-property-inclusion=non_null diff --git a/custom/v3/common/configuration/v3.env b/custom/v3/common/configuration/v3.env index 95fa8a860b7..58b503be69d 100644 --- a/custom/v3/common/configuration/v3.env +++ b/custom/v3/common/configuration/v3.env @@ -17,4 +17,4 @@ # under the License. # -SPRING_PROFILES_ACTIVE=v3,test,diagnostics +SPRING_PROFILES_ACTIVE=v3,test,diagnostics \ No newline at end of file diff --git a/custom/v3/note/api/dependencies.gradle b/custom/v3/note/api/dependencies.gradle index 704204e6217..ef503560f1a 100644 --- a/custom/v3/note/api/dependencies.gradle +++ b/custom/v3/note/api/dependencies.gradle @@ -20,7 +20,11 @@ dependencies { implementation(project(':fineract-core')) implementation(project(':fineract-provider')) - implementation('org.springframework.boot:spring-boot-starter-web') + implementation(project(':custom:v3:note:data')) + implementation(project(':custom:v3:command:data')) + implementation(project(':custom:v3:command:service')) implementation('org.springframework.boot:spring-boot-starter') implementation('io.swagger.core.v3:swagger-annotations-jakarta') + implementation('org.springframework.boot:spring-boot-starter-web') + implementation('org.springframework.boot:spring-boot-starter-validation') } diff --git a/custom/v3/note/api/src/main/java/org/apache/fineract/v3/note/api/NotesCommandsApi.java b/custom/v3/note/api/src/main/java/org/apache/fineract/v3/note/api/NotesCommandsApi.java new file mode 100644 index 00000000000..e6443c41c04 --- /dev/null +++ b/custom/v3/note/api/src/main/java/org/apache/fineract/v3/note/api/NotesCommandsApi.java @@ -0,0 +1,130 @@ +/** + * 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.v3.note.api; + +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.http.MediaType.APPLICATION_PROBLEM_JSON_VALUE; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.portfolio.note.domain.NoteType; +import org.apache.fineract.portfolio.note.exception.NoteResourceNotSupportedException; +import org.apache.fineract.v3.command.service.CommandDispatcher; +import org.apache.fineract.v3.note.data.NoteCreateRequest; +import org.apache.fineract.v3.note.data.NoteCreateResponse; +import org.apache.fineract.v3.note.data.NoteDeleteRequest; +import org.apache.fineract.v3.note.data.NoteDeleteResponse; +import org.apache.fineract.v3.note.data.NoteRequestBody; +import org.apache.fineract.v3.note.data.NoteUpdateRequest; +import org.apache.fineract.v3.note.data.NoteUpdateResponse; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping(value = "/v3/{resourceType}/{resourceId}/notes", produces = { APPLICATION_JSON_VALUE, APPLICATION_PROBLEM_JSON_VALUE }) +@Tag(name = "Notes", description = "Notes API allows to manage notes for supported resources. (Commands)") +@RequiredArgsConstructor +public class NotesCommandsApi { + + private final CommandDispatcher commandDispatcher; + + @PostMapping + @Operation(summary = "Add a Resource Note", description = """ + Adds a new note to a supported resource. + + Example Requests: + + clients/1/notes + + groups/1/notes""") + public NoteCreateResponse addNewNote(@PathVariable("resourceType") final String resourceType, + @PathVariable("resourceId") final Long resourceId, @Valid @RequestBody NoteRequestBody apiRequestBody) { + + final NoteType noteType = NoteType.fromApiUrl(resourceType); + + if (noteType == null) { + throw new NoteResourceNotSupportedException(resourceType); + } + + // TODO: set more metadata (username, tenant ID ...etc.) + var noteCreateRequest = NoteCreateRequest.builder() // + .resourceId(resourceId) // + .noteType(noteType) // + .requestBody(apiRequestBody) // + .build(); + + return (NoteCreateResponse) commandDispatcher.dispatch(noteCreateRequest); + } + + @PutMapping("{noteId}") + @Operation(summary = "Update a Resource Note", description = "Updates a Resource Note") + public NoteUpdateResponse updateNote(@PathVariable("resourceType") @Parameter(description = "resourceType") final String resourceType, + @PathVariable("resourceId") @Parameter(description = "resourceId") final Long resourceId, + @PathVariable("noteId") @Parameter(description = "noteId") final Long noteId, + @Valid @RequestBody NoteRequestBody apiRequestBody) { + + final NoteType noteType = NoteType.fromApiUrl(resourceType); + + if (noteType == null) { + throw new NoteResourceNotSupportedException(resourceType); + } + + // TODO: set more metadata (username, tenant ID ...etc.) + var noteUpdateRequest = NoteUpdateRequest.builder() // + .resourceId(resourceId) // + .noteId(noteId) // + .noteType(noteType) // + .requestBody(apiRequestBody) // + .build(); + + return (NoteUpdateResponse) commandDispatcher.dispatch(noteUpdateRequest); + } + + @DeleteMapping("{noteId}") + @Operation(summary = "Delete a Resource Note", description = "Deletes a Resource Note") + public NoteDeleteResponse deleteNote(@PathVariable("resourceType") @Parameter(description = "resourceType") final String resourceType, + @PathVariable("resourceId") @Parameter(description = "resourceId") final Long resourceId, + @PathVariable("noteId") @Parameter(description = "noteId") final Long noteId) { + + final NoteType noteType = NoteType.fromApiUrl(resourceType); + + if (noteType == null) { + throw new NoteResourceNotSupportedException(resourceType); + } + + // TODO: set more metadata (username, tenant ID ...etc.) + var noteDeleteRequest = NoteDeleteRequest.builder() // + .resourceId(resourceId) // + .noteId(noteId) // + .noteType(noteType) // + .build(); + + return (NoteDeleteResponse) commandDispatcher.dispatch(noteDeleteRequest); + } + +} diff --git a/custom/v3/note/configuration/src/main/java/org/apache/fineract/v3/note/configuration/NoteSecurityConfiguration.java b/custom/v3/note/configuration/src/main/java/org/apache/fineract/v3/note/configuration/NoteSecurityConfiguration.java index b421bfbfde8..d684122b538 100644 --- a/custom/v3/note/configuration/src/main/java/org/apache/fineract/v3/note/configuration/NoteSecurityConfiguration.java +++ b/custom/v3/note/configuration/src/main/java/org/apache/fineract/v3/note/configuration/NoteSecurityConfiguration.java @@ -23,24 +23,49 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.web.SecurityFilterChain; @Configuration +@EnableMethodSecurity public class NoteSecurityConfiguration { @Bean @Order(2) public SecurityFilterChain notesSecurityFilterChain(HttpSecurity http) throws Exception { - return http.securityMatcher(antMatcher("/v3/*/*/notes")).authorizeHttpRequests((auth) -> { // - auth.requestMatchers("/v3/clients/*/notes").hasAuthority("READ_CLIENTNOTE") // - .requestMatchers("/v3/groups/*/notes").hasAuthority("READ_GROUPNOTE") // - .requestMatchers("/v3/loans/*/notes").hasAuthority("READ_LOANNOTE") // - .requestMatchers("/v3/loanTransactions/*/notes").hasAuthority("READ_LOANTRANSACTIONNOTE") // - .requestMatchers("/v3/savings/*/notes").hasAuthority("READ_SAVINGNOTE") // - // - // - .anyRequest().hasAnyAuthority("ALL_FUNCTIONS", "ALL_FUNCTIONS_READ"); - }).build(); + return http.securityMatcher(antMatcher("/v3/**/notes/**")).authorizeHttpRequests(auth -> auth + + // Read Requests (GET) + .requestMatchers(HttpMethod.GET, "/v3/clients/*/notes").hasAuthority("READ_CLIENTNOTE") + .requestMatchers(HttpMethod.GET, "/v3/groups/*/notes").hasAuthority("READ_GROUPNOTE") + .requestMatchers(HttpMethod.GET, "/v3/loans/*/notes").hasAuthority("READ_LOANNOTE") + .requestMatchers(HttpMethod.GET, "/v3/loanTransactions/*/notes").hasAuthority("READ_LOANTRANSACTIONNOTE") + .requestMatchers(HttpMethod.GET, "/v3/savings/*/notes").hasAuthority("READ_SAVINGNOTE") + + // Write Requests (POST) + .requestMatchers(HttpMethod.POST, "/v3/clients/*/notes").hasAuthority("CREATE_CLIENTNOTE") + .requestMatchers(HttpMethod.POST, "/v3/groups/*/notes").hasAuthority("CREATE_GROUPNOTE") + .requestMatchers(HttpMethod.POST, "/v3/loans/*/notes").hasAuthority("CREATE_LOANNOTE") + .requestMatchers(HttpMethod.POST, "/v3/loanTransactions/*/notes").hasAuthority("CREATE_LOANTRANSACTIONNOTE") + .requestMatchers(HttpMethod.POST, "/v3/savings/*/notes").hasAuthority("CREATE_SAVINGNOTE") + + // Update Requests (PUT) + .requestMatchers(HttpMethod.PUT, "/v3/clients/*/notes/*").hasAuthority("UPDATE_CLIENTNOTE") + .requestMatchers(HttpMethod.PUT, "/v3/groups/*/notes/*").hasAuthority("UPDATE_GROUPNOTE") + .requestMatchers(HttpMethod.PUT, "/v3/loans/*/notes/*").hasAuthority("UPDATE_LOANNOTE") + .requestMatchers(HttpMethod.PUT, "/v3/loanTransactions/*/notes/*").hasAuthority("UPDATE_LOANTRANSACTIONNOTE") + .requestMatchers(HttpMethod.PUT, "/v3/savings/*/notes/*").hasAuthority("UPDATE_SAVINGNOTE") + + // Delete Requests (DELETE) + .requestMatchers(HttpMethod.DELETE, "/v3/clients/*/notes/*").hasAuthority("DELETE_CLIENTNOTE") + .requestMatchers(HttpMethod.DELETE, "/v3/groups/*/notes/*").hasAuthority("DELETE_GROUPNOTE") + .requestMatchers(HttpMethod.DELETE, "/v3/loans/*/notes/*").hasAuthority("DELETE_LOANNOTE") + .requestMatchers(HttpMethod.DELETE, "/v3/loanTransactions/*/notes/*").hasAuthority("DELETE_LOANTRANSACTIONNOTE") + .requestMatchers(HttpMethod.DELETE, "/v3/savings/*/notes/*").hasAuthority("DELETE_SAVINGNOTE") + + // Catch-all rule + .anyRequest().hasAuthority("ALL_FUNCTIONS")).build(); } } diff --git a/custom/v3/note/data/build.gradle b/custom/v3/note/data/build.gradle new file mode 100644 index 00000000000..3f447b740b4 --- /dev/null +++ b/custom/v3/note/data/build.gradle @@ -0,0 +1,25 @@ +/** + * 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. + */ +description = 'The new generation of the API layer (v3): Note Data' + +group = 'org.apache.fineract.v3.note' + +archivesBaseName = 'fineract-custom-note-data' + +apply from: 'dependencies.gradle' diff --git a/custom/v3/note/data/dependencies.gradle b/custom/v3/note/data/dependencies.gradle new file mode 100644 index 00000000000..8828d26d829 --- /dev/null +++ b/custom/v3/note/data/dependencies.gradle @@ -0,0 +1,26 @@ +/** + * 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. + */ + +dependencies { + + implementation(project(':fineract-core')) + implementation(project(':custom:v3:command:data')) + implementation('org.springframework.boot:spring-boot-starter-validation') + implementation('com.fasterxml.jackson.core:jackson-databind') +} diff --git a/custom/v3/note/data/src/main/java/org/apache/fineract/v3/note/data/NoteCreateRequest.java b/custom/v3/note/data/src/main/java/org/apache/fineract/v3/note/data/NoteCreateRequest.java new file mode 100644 index 00000000000..728ebfb9ae6 --- /dev/null +++ b/custom/v3/note/data/src/main/java/org/apache/fineract/v3/note/data/NoteCreateRequest.java @@ -0,0 +1,43 @@ +/** + * 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.v3.note.data; + +import jakarta.validation.constraints.NotNull; +import java.io.Serial; +import lombok.Getter; +import lombok.experimental.FieldNameConstants; +import lombok.experimental.SuperBuilder; +import org.apache.fineract.portfolio.note.domain.NoteType; +import org.apache.fineract.v3.command.data.CommandRequest; + +@Getter +@SuperBuilder +@FieldNameConstants +public class NoteCreateRequest extends CommandRequest { + + @Serial + private static final long serialVersionUID = 1L; + + @NotNull(message = "Resource ID can never be null") + private final Long resourceId; + + @NotNull(message = "Note Type can never be null") + private final NoteType noteType; +} diff --git a/custom/v3/note/data/src/main/java/org/apache/fineract/v3/note/data/NoteCreateResponse.java b/custom/v3/note/data/src/main/java/org/apache/fineract/v3/note/data/NoteCreateResponse.java new file mode 100644 index 00000000000..fda55b5cdf8 --- /dev/null +++ b/custom/v3/note/data/src/main/java/org/apache/fineract/v3/note/data/NoteCreateResponse.java @@ -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.v3.note.data; + +import java.io.Serial; +import lombok.Getter; +import lombok.experimental.FieldNameConstants; +import lombok.experimental.SuperBuilder; +import org.apache.fineract.v3.command.data.CommandResponse; + +@Getter +@SuperBuilder +@FieldNameConstants +public class NoteCreateResponse extends CommandResponse { + + @Serial + private static final long serialVersionUID = 1L; +} diff --git a/custom/v3/note/data/src/main/java/org/apache/fineract/v3/note/data/NoteDeleteRequest.java b/custom/v3/note/data/src/main/java/org/apache/fineract/v3/note/data/NoteDeleteRequest.java new file mode 100644 index 00000000000..8a007911303 --- /dev/null +++ b/custom/v3/note/data/src/main/java/org/apache/fineract/v3/note/data/NoteDeleteRequest.java @@ -0,0 +1,42 @@ +/** + * 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.v3.note.data; + +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.experimental.FieldNameConstants; +import lombok.experimental.SuperBuilder; +import org.apache.fineract.portfolio.note.domain.NoteType; +import org.apache.fineract.v3.command.data.CommandRequest; + +@Getter +@SuperBuilder +@FieldNameConstants +public class NoteDeleteRequest extends CommandRequest { + + @NotNull(message = "Resource ID can not be null") + private final Long resourceId; + + @NotNull(message = "Note ID can not be null") + private final Long noteId; + + @NotNull(message = "Resource Type can not be null") + private final NoteType noteType; +} diff --git a/custom/v3/note/data/src/main/java/org/apache/fineract/v3/note/data/NoteDeleteResponse.java b/custom/v3/note/data/src/main/java/org/apache/fineract/v3/note/data/NoteDeleteResponse.java new file mode 100644 index 00000000000..d5c15594e4c --- /dev/null +++ b/custom/v3/note/data/src/main/java/org/apache/fineract/v3/note/data/NoteDeleteResponse.java @@ -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.v3.note.data; + +import java.io.Serial; +import lombok.Getter; +import lombok.experimental.FieldNameConstants; +import lombok.experimental.SuperBuilder; +import org.apache.fineract.v3.command.data.CommandResponse; + +@Getter +@SuperBuilder +@FieldNameConstants +public class NoteDeleteResponse extends CommandResponse { + + @Serial + private static final long serialVersionUID = 1L; +} diff --git a/custom/v3/note/data/src/main/java/org/apache/fineract/v3/note/data/NoteRequestBody.java b/custom/v3/note/data/src/main/java/org/apache/fineract/v3/note/data/NoteRequestBody.java new file mode 100644 index 00000000000..c958b7771a8 --- /dev/null +++ b/custom/v3/note/data/src/main/java/org/apache/fineract/v3/note/data/NoteRequestBody.java @@ -0,0 +1,38 @@ +/** + * 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.v3.note.data; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class NoteRequestBody { + + @NotBlank(message = "Note cannot be null or blank") + @Size(max = 1000, message = "Note cannot exceed 1000 characters") + private String note; +} diff --git a/custom/v3/note/data/src/main/java/org/apache/fineract/v3/note/data/NoteResponseData.java b/custom/v3/note/data/src/main/java/org/apache/fineract/v3/note/data/NoteResponseData.java new file mode 100644 index 00000000000..b6e9cffb156 --- /dev/null +++ b/custom/v3/note/data/src/main/java/org/apache/fineract/v3/note/data/NoteResponseData.java @@ -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.v3.note.data; + +import java.util.Map; +import lombok.Data; +import lombok.experimental.FieldNameConstants; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@FieldNameConstants +public class NoteResponseData { + + private final Long resourceId; + private final Long officeId; + private final Long clientId; + private final Long groupId; + private final Long savingsId; + private final Long loanId; + private final Long loanTransactionId; + + Map changes; +} diff --git a/custom/v3/note/data/src/main/java/org/apache/fineract/v3/note/data/NoteUpdateRequest.java b/custom/v3/note/data/src/main/java/org/apache/fineract/v3/note/data/NoteUpdateRequest.java new file mode 100644 index 00000000000..ff3eef602a8 --- /dev/null +++ b/custom/v3/note/data/src/main/java/org/apache/fineract/v3/note/data/NoteUpdateRequest.java @@ -0,0 +1,42 @@ +/** + * 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.v3.note.data; + +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.experimental.FieldNameConstants; +import lombok.experimental.SuperBuilder; +import org.apache.fineract.portfolio.note.domain.NoteType; +import org.apache.fineract.v3.command.data.CommandRequest; + +@Getter +@SuperBuilder +@FieldNameConstants +public class NoteUpdateRequest extends CommandRequest { + + @NotNull(message = "Resource ID can not be null") + private final Long resourceId; + + @NotNull(message = "Note ID can not be null") + private final Long noteId; + + @NotNull(message = "Note Type can not be null") + private final NoteType noteType; +} diff --git a/custom/v3/note/data/src/main/java/org/apache/fineract/v3/note/data/NoteUpdateResponse.java b/custom/v3/note/data/src/main/java/org/apache/fineract/v3/note/data/NoteUpdateResponse.java new file mode 100644 index 00000000000..e25c4adb333 --- /dev/null +++ b/custom/v3/note/data/src/main/java/org/apache/fineract/v3/note/data/NoteUpdateResponse.java @@ -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.v3.note.data; + +import java.io.Serial; +import lombok.Getter; +import lombok.experimental.FieldNameConstants; +import lombok.experimental.SuperBuilder; +import org.apache.fineract.v3.command.data.CommandResponse; + +@Getter +@SuperBuilder +@FieldNameConstants +public class NoteUpdateResponse extends CommandResponse { + + @Serial + private static final long serialVersionUID = 1L; +} diff --git a/custom/v3/note/handler/build.gradle b/custom/v3/note/handler/build.gradle new file mode 100644 index 00000000000..b9209ce0d99 --- /dev/null +++ b/custom/v3/note/handler/build.gradle @@ -0,0 +1,25 @@ +/** + * 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. + */ +description = 'The new generation of the API layer (v3): Note Handler' + +group = 'org.apache.fineract.v3.note' + +archivesBaseName = 'fineract-custom-note-handler' + +apply from: 'dependencies.gradle' diff --git a/custom/v3/note/handler/dependencies.gradle b/custom/v3/note/handler/dependencies.gradle new file mode 100644 index 00000000000..a31c49db4c0 --- /dev/null +++ b/custom/v3/note/handler/dependencies.gradle @@ -0,0 +1,28 @@ +/** + * 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. + */ + +dependencies { + implementation(project(':fineract-core')) + implementation(project(':custom:v3:note:data')) + implementation(project(':custom:v3:note:service')) + implementation(project(':custom:v3:command:data')) + implementation(project(':custom:v3:command:service')) + implementation('org.springframework.boot:spring-boot-starter') + implementation('org.springframework.boot:spring-boot-starter-data-jpa') +} diff --git a/custom/v3/note/handler/src/main/java/org/apache/fineract/v3/note/handler/CreateNoteHandler.java b/custom/v3/note/handler/src/main/java/org/apache/fineract/v3/note/handler/CreateNoteHandler.java new file mode 100644 index 00000000000..bb8c8ffccca --- /dev/null +++ b/custom/v3/note/handler/src/main/java/org/apache/fineract/v3/note/handler/CreateNoteHandler.java @@ -0,0 +1,46 @@ +/** + * 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.v3.note.handler; + +import lombok.RequiredArgsConstructor; +import org.apache.fineract.v3.command.service.CommandHandler; +import org.apache.fineract.v3.note.data.NoteCreateRequest; +import org.apache.fineract.v3.note.data.NoteCreateResponse; +import org.apache.fineract.v3.note.service.NoteWritePlatformService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class CreateNoteHandler implements CommandHandler { + + private final NoteWritePlatformService writePlatformService; + + @Override + public Class getRequestType() { + return NoteCreateRequest.class; + } + + @Transactional + @Override + public NoteCreateResponse handle(NoteCreateRequest request) { + return writePlatformService.createNote(request); + } +} diff --git a/custom/v3/note/handler/src/main/java/org/apache/fineract/v3/note/handler/DeleteNoteHandler.java b/custom/v3/note/handler/src/main/java/org/apache/fineract/v3/note/handler/DeleteNoteHandler.java new file mode 100644 index 00000000000..0ad601c5cb1 --- /dev/null +++ b/custom/v3/note/handler/src/main/java/org/apache/fineract/v3/note/handler/DeleteNoteHandler.java @@ -0,0 +1,46 @@ +/** + * 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.v3.note.handler; + +import lombok.RequiredArgsConstructor; +import org.apache.fineract.v3.command.service.CommandHandler; +import org.apache.fineract.v3.note.data.NoteDeleteRequest; +import org.apache.fineract.v3.note.data.NoteDeleteResponse; +import org.apache.fineract.v3.note.service.NoteWritePlatformService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class DeleteNoteHandler implements CommandHandler { + + private final NoteWritePlatformService writePlatformService; + + @Override + public Class getRequestType() { + return NoteDeleteRequest.class; + } + + @Transactional + @Override + public NoteDeleteResponse handle(NoteDeleteRequest request) { + return writePlatformService.deleteNote(request); + } +} diff --git a/custom/v3/note/handler/src/main/java/org/apache/fineract/v3/note/handler/UpdateNoteHandler.java b/custom/v3/note/handler/src/main/java/org/apache/fineract/v3/note/handler/UpdateNoteHandler.java new file mode 100644 index 00000000000..a8c90b3795e --- /dev/null +++ b/custom/v3/note/handler/src/main/java/org/apache/fineract/v3/note/handler/UpdateNoteHandler.java @@ -0,0 +1,46 @@ +/** + * 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.v3.note.handler; + +import lombok.RequiredArgsConstructor; +import org.apache.fineract.v3.command.service.CommandHandler; +import org.apache.fineract.v3.note.data.NoteUpdateRequest; +import org.apache.fineract.v3.note.data.NoteUpdateResponse; +import org.apache.fineract.v3.note.service.NoteWritePlatformService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class UpdateNoteHandler implements CommandHandler { + + private final NoteWritePlatformService writePlatformService; + + @Override + public Class getRequestType() { + return NoteUpdateRequest.class; + } + + @Transactional + @Override + public NoteUpdateResponse handle(NoteUpdateRequest request) { + return writePlatformService.updateNote(request); + } +} diff --git a/custom/v3/note/service/build.gradle b/custom/v3/note/service/build.gradle new file mode 100644 index 00000000000..56266a12adc --- /dev/null +++ b/custom/v3/note/service/build.gradle @@ -0,0 +1,25 @@ +/** + * 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. + */ +description = 'The new generation of the API layer (v3): Note services' + +group = 'org.apache.fineract.v3.note' + +archivesBaseName = 'fineract-custom-note-service' + +apply from: 'dependencies.gradle' diff --git a/custom/v3/note/service/dependencies.gradle b/custom/v3/note/service/dependencies.gradle new file mode 100644 index 00000000000..dbaaa1ec302 --- /dev/null +++ b/custom/v3/note/service/dependencies.gradle @@ -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. + */ + +dependencies { + implementation(project(':fineract-core')) + implementation(project(':fineract-loan')) + implementation(project(':fineract-savings')) + implementation(project(':fineract-provider')) + implementation(project(':custom:v3:note:data')) + implementation(project(':custom:v3:command:data')) + + implementation ('org.springframework.boot:spring-boot-starter-data-jpa') { + exclude group: 'org.hibernate' + } + + implementation('org.eclipse.persistence:org.eclipse.persistence.jpa') { + exclude group: 'org.eclipse.persistence', module: 'jakarta.persistence' + } +} diff --git a/custom/v3/note/service/src/main/java/org/apache/fineract/v3/note/service/NoteWritePlatformService.java b/custom/v3/note/service/src/main/java/org/apache/fineract/v3/note/service/NoteWritePlatformService.java new file mode 100644 index 00000000000..581ba69e1f7 --- /dev/null +++ b/custom/v3/note/service/src/main/java/org/apache/fineract/v3/note/service/NoteWritePlatformService.java @@ -0,0 +1,36 @@ +/** + * 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.v3.note.service; + +import org.apache.fineract.v3.note.data.NoteCreateRequest; +import org.apache.fineract.v3.note.data.NoteCreateResponse; +import org.apache.fineract.v3.note.data.NoteDeleteRequest; +import org.apache.fineract.v3.note.data.NoteDeleteResponse; +import org.apache.fineract.v3.note.data.NoteUpdateRequest; +import org.apache.fineract.v3.note.data.NoteUpdateResponse; + +public interface NoteWritePlatformService { + + NoteCreateResponse createNote(NoteCreateRequest request); + + NoteUpdateResponse updateNote(NoteUpdateRequest request); + + NoteDeleteResponse deleteNote(NoteDeleteRequest request); + +} diff --git a/custom/v3/note/service/src/main/java/org/apache/fineract/v3/note/service/NoteWritePlatformServiceImpl.java b/custom/v3/note/service/src/main/java/org/apache/fineract/v3/note/service/NoteWritePlatformServiceImpl.java new file mode 100644 index 00000000000..2b82a78f934 --- /dev/null +++ b/custom/v3/note/service/src/main/java/org/apache/fineract/v3/note/service/NoteWritePlatformServiceImpl.java @@ -0,0 +1,196 @@ +/** + * 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.v3.note.service; + +import java.util.HashMap; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.portfolio.client.domain.Client; +import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper; +import org.apache.fineract.portfolio.group.domain.Group; +import org.apache.fineract.portfolio.group.domain.GroupRepositoryWrapper; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository; +import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionNotFoundException; +import org.apache.fineract.portfolio.note.domain.Note; +import org.apache.fineract.portfolio.note.domain.NoteRepository; +import org.apache.fineract.portfolio.note.exception.NoteNotFoundException; +import org.apache.fineract.portfolio.note.exception.NoteResourceNotSupportedException; +import org.apache.fineract.portfolio.savings.domain.SavingsAccount; +import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepositoryWrapper; +import org.apache.fineract.v3.note.data.NoteCreateRequest; +import org.apache.fineract.v3.note.data.NoteCreateResponse; +import org.apache.fineract.v3.note.data.NoteDeleteRequest; +import org.apache.fineract.v3.note.data.NoteDeleteResponse; +import org.apache.fineract.v3.note.data.NoteResponseData; +import org.apache.fineract.v3.note.data.NoteUpdateRequest; +import org.apache.fineract.v3.note.data.NoteUpdateResponse; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor +public class NoteWritePlatformServiceImpl implements NoteWritePlatformService { + + private static final String NOTE = "note"; + + private final ClientRepositoryWrapper clientRepository; + private final GroupRepositoryWrapper groupRepository; + private final LoanRepositoryWrapper loanRepository; + private final SavingsAccountRepositoryWrapper savingsAccountRepository; + private final LoanTransactionRepository loanTransactionRepository; + private final NoteRepository noteRepository; + + @Override + public NoteCreateResponse createNote(NoteCreateRequest createRequest) { + + Note newNoteEntity; + Long officeId = null; + Long clientId = null; + Long groupId = null; + Long loanId = null; + Long savingsId = null; + + switch (createRequest.getNoteType()) { + case CLIENT -> { + clientId = createRequest.getResourceId(); + Client client = clientRepository.findOneWithNotFoundDetection(clientId); + newNoteEntity = new Note(client, createRequest.getRequestBody().getNote()); + officeId = client.officeId(); + } + case GROUP -> { + groupId = createRequest.getResourceId(); + Group group = groupRepository.findOneWithNotFoundDetection(groupId); + newNoteEntity = new Note(group, createRequest.getRequestBody().getNote()); + officeId = group.officeId(); + } + case LOAN -> { + loanId = createRequest.getResourceId(); + Loan loan = loanRepository.findOneWithNotFoundDetection(loanId); + newNoteEntity = Note.loanNote(loan, createRequest.getRequestBody().getNote()); + officeId = loan.getOfficeId(); + } + case SAVING_ACCOUNT -> { + savingsId = createRequest.getResourceId(); + SavingsAccount savingAccount = savingsAccountRepository.findOneWithNotFoundDetection(savingsId); + officeId = savingAccount.getClient().getOffice().getId(); + newNoteEntity = Note.savingNote(savingAccount, createRequest.getRequestBody().getNote()); + } + case LOAN_TRANSACTION -> { + Long loanTransactionId = createRequest.getResourceId(); + LoanTransaction loanTransaction = loanTransactionRepository.findById(loanTransactionId) + .orElseThrow(() -> new LoanTransactionNotFoundException(createRequest.getResourceId())); + Loan loan = loanTransaction.getLoan(); + newNoteEntity = Note.loanTransactionNote(loan, loanTransaction, createRequest.getRequestBody().getNote()); + officeId = loan.getOfficeId(); + loanId = loan.getId(); + } + default -> throw new NoteResourceNotSupportedException(createRequest.getNoteType().name()); + } + + noteRepository.saveAndFlush(newNoteEntity); + return buildNoteCreateResponse(newNoteEntity.getId(), clientId, officeId, groupId, loanId, savingsId); + } + + @Override + public NoteUpdateResponse updateNote(NoteUpdateRequest updateRequest) { + + Long officeId = null; + Long clientId = null; + Long groupId = null; + Long loanId = null; + Long savingsId = null; + Long noteId = updateRequest.getNoteId(); + Note persistedNote = noteRepository.findById(noteId).orElseThrow(() -> new NoteNotFoundException(noteId)); + + switch (updateRequest.getNoteType()) { + case CLIENT -> { + clientId = updateRequest.getResourceId(); + Client client = clientRepository.findOneWithNotFoundDetection(clientId); + officeId = client.officeId(); + } + case GROUP -> { + groupId = updateRequest.getResourceId(); + Group group = groupRepository.findOneWithNotFoundDetection(groupId); + officeId = group.officeId(); + } + case LOAN -> { + loanId = updateRequest.getResourceId(); + Loan loan = loanRepository.findOneWithNotFoundDetection(loanId); + officeId = loan.getOfficeId(); + } + case LOAN_TRANSACTION -> { + Long loanTransactionId = updateRequest.getResourceId(); + LoanTransaction loanTransaction = loanTransactionRepository.findById(loanTransactionId) + .orElseThrow(() -> new LoanTransactionNotFoundException(loanTransactionId)); + Loan loan = loanTransaction.getLoan(); + officeId = loan.getOfficeId(); + loanId = loan.getId(); + } + case SAVING_ACCOUNT -> { + savingsId = updateRequest.getResourceId(); + SavingsAccount savingAccount = savingsAccountRepository.findOneWithNotFoundDetection(savingsId); + officeId = savingAccount.getClient().getOffice().getId(); + } + default -> throw new NoteResourceNotSupportedException(updateRequest.getNoteType().name()); + } + + String newNote = updateRequest.getRequestBody().getNote(); + Map changes = new HashMap<>(); + + if (!persistedNote.getNote().equals(newNote)) { + persistedNote.setNote(newNote); + noteRepository.saveAndFlush(persistedNote); + changes.put(NOTE, newNote); + } + + return buildNoteUpdateResponse(persistedNote.getId(), clientId, officeId, groupId, loanId, savingsId, changes); + } + + @Override + public NoteDeleteResponse deleteNote(NoteDeleteRequest deleteRequest) { + Long noteId = deleteRequest.getNoteId(); + Note note = noteRepository.findById(noteId).orElseThrow(() -> new NoteNotFoundException(noteId)); + noteRepository.delete(note); + var noteResponseData = NoteResponseData.builder().resourceId(noteId).build(); + return NoteDeleteResponse.builder().data(noteResponseData).build(); + } + + private NoteCreateResponse buildNoteCreateResponse(Long noteId, Long clientId, Long officeId, Long groupId, Long loanId, + Long savingsId) { + + var noteResponseData = NoteResponseData.builder().resourceId(noteId).clientId(clientId).officeId(officeId).groupId(groupId) + .loanId(loanId).savingsId(savingsId).build(); + + return NoteCreateResponse.builder().data(noteResponseData).build(); + } + + private NoteUpdateResponse buildNoteUpdateResponse(Long noteId, Long clientId, Long officeId, Long groupId, Long loanId, Long savingsId, + Map changes) { + + var noteResponseData = NoteResponseData.builder().resourceId(noteId).clientId(clientId).officeId(officeId).groupId(groupId) + .loanId(loanId).savingsId(savingsId).changes(changes).build(); + + return NoteUpdateResponse.builder().data(noteResponseData).build(); + } +} diff --git a/custom/v3/note/starter/dependencies.gradle b/custom/v3/note/starter/dependencies.gradle index 786157b1cea..e4ef8b9cb71 100644 --- a/custom/v3/note/starter/dependencies.gradle +++ b/custom/v3/note/starter/dependencies.gradle @@ -19,8 +19,14 @@ dependencies { implementation( + project(':fineract-core'), + project(':fineract-provider'), + project(':fineract-loan'), + project(':fineract-savings'), project(':custom:v3:note:core'), project(':custom:v3:note:api'), + project(':custom:v3:note:service') ) + compileOnly('org.springframework.boot:spring-boot-autoconfigure') } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/domain/Note.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/domain/Note.java index ecc28837fc5..02dda4bd7b0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/domain/Note.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/domain/Note.java @@ -25,6 +25,8 @@ import jakarta.persistence.Table; import java.util.LinkedHashMap; import java.util.Map; +import lombok.Getter; +import lombok.Setter; import org.apache.commons.lang3.StringUtils; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom; @@ -37,6 +39,7 @@ import org.apache.fineract.portfolio.shareaccounts.domain.ShareAccount; @Entity +@Getter @Table(name = "m_note") public class Note extends AbstractAuditableWithUTCDateTimeCustom { @@ -56,6 +59,7 @@ public class Note extends AbstractAuditableWithUTCDateTimeCustom { @JoinColumn(name = "loan_transaction_id", nullable = true) private LoanTransaction loanTransaction; + @Setter @Column(name = "note", length = 1000) private String note; @@ -119,7 +123,7 @@ public Note(final Client client, final String note) { this.noteTypeId = NoteType.CLIENT.getValue(); } - private Note(final Group group, final String note) { + public Note(final Group group, final String note) { this.group = group; this.note = note; this.client = null; @@ -179,8 +183,4 @@ public Map update(final JsonCommand command) { public boolean isNotAgainstClientWithIdOf(final Long clientId) { return !this.client.identifiedBy(clientId); } - - public String getNote() { - return note; - } }