FINERACT-2169: New command processing infrastructure
This commit is contained in:
committed by
Aleksandar Vidakovic
parent
fd45f43f36
commit
8d83b5547c
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* 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.command.core;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.UUID;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
|
||||
@Data
|
||||
@FieldNameConstants
|
||||
public class Command<T> implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private UUID id;
|
||||
|
||||
private OffsetDateTime createdAt;
|
||||
|
||||
private String tenantId;
|
||||
|
||||
private String username;
|
||||
|
||||
private T payload;
|
||||
}
|
||||
@@ -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.command.core;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
@UtilityClass
|
||||
public final class CommandConstants {
|
||||
|
||||
public static final String COMMAND_REQUEST_ID = "x-fineract-request-id";
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
package org.apache.fineract.command.core;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public interface CommandExecutor {
|
||||
|
||||
<REQ, RES> Supplier<RES> execute(Command<REQ> command);
|
||||
}
|
||||
@@ -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.command.core;
|
||||
|
||||
import com.google.common.reflect.TypeToken;
|
||||
|
||||
public interface CommandHandler<REQ, RES> {
|
||||
|
||||
RES handle(Command<REQ> command);
|
||||
|
||||
default boolean matches(Command<REQ> command) {
|
||||
TypeToken<REQ> handlerType = new TypeToken<>(getClass()) {};
|
||||
|
||||
return handlerType.getRawType().isAssignableFrom(command.getPayload().getClass());
|
||||
}
|
||||
}
|
||||
+25
@@ -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.command.core;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface CommandMiddleware {
|
||||
|
||||
void invoke(Command<?> command);
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
package org.apache.fineract.command.core;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public interface CommandPipeline {
|
||||
|
||||
<REQ, RES> Supplier<RES> send(Command<REQ> command);
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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.command.core;
|
||||
|
||||
import com.lmax.disruptor.dsl.ProducerType;
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
@Builder
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@FieldNameConstants
|
||||
@ConfigurationProperties(prefix = "fineract.command")
|
||||
public final class CommandProperties implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Builder.Default
|
||||
private Boolean enabled = true;
|
||||
|
||||
@Builder.Default
|
||||
private CommandExecutorType executor = CommandExecutorType.sync;
|
||||
|
||||
@Builder.Default
|
||||
private Integer ringBufferSize = 1024;
|
||||
|
||||
@Builder.Default
|
||||
private ProducerType producerType = ProducerType.SINGLE;
|
||||
|
||||
public enum CommandExecutorType {
|
||||
sync, async, disruptor
|
||||
}
|
||||
}
|
||||
@@ -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.command.core;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface CommandRouter {
|
||||
|
||||
<REQ, RES> CommandHandler<REQ, RES> route(Command<REQ> command);
|
||||
}
|
||||
+28
@@ -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.command.core.exception;
|
||||
|
||||
import org.apache.fineract.command.core.Command;
|
||||
|
||||
public class CommandHandlerNotFoundException extends RuntimeException {
|
||||
|
||||
public CommandHandlerNotFoundException(Command<?> command) {
|
||||
super("Cannot find a matching handler for " + (command != null ? command.getClass().getSimpleName() : "UKNOWN") + " command");
|
||||
}
|
||||
}
|
||||
+28
@@ -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.command.core.exception;
|
||||
|
||||
import org.apache.fineract.command.core.Command;
|
||||
|
||||
public class CommandIllegalArgumentException extends RuntimeException {
|
||||
|
||||
public CommandIllegalArgumentException(Command<?> command, String message) {
|
||||
super("Illegal argument for " + command.getClass().getSimpleName() + " command: " + message);
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* 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.command.implementation;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Supplier;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.command.core.Command;
|
||||
import org.apache.fineract.command.core.CommandExecutor;
|
||||
import org.apache.fineract.command.core.CommandHandler;
|
||||
import org.apache.fineract.command.core.CommandMiddleware;
|
||||
import org.apache.fineract.command.core.CommandRouter;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
@ConditionalOnProperty(value = "fineract.command.executor", havingValue = "async")
|
||||
public class AsynchronousCommandExecutor implements CommandExecutor {
|
||||
|
||||
private final List<CommandMiddleware> middlewares;
|
||||
|
||||
private final CommandRouter router;
|
||||
|
||||
@Override
|
||||
public <REQ, RES> Supplier<RES> execute(Command<REQ> command) {
|
||||
CompletableFuture<RES> future = CompletableFuture.supplyAsync(() -> {
|
||||
for (CommandMiddleware middleware : middlewares) {
|
||||
middleware.invoke(command);
|
||||
}
|
||||
|
||||
CommandHandler<REQ, RES> handler = router.route(command);
|
||||
|
||||
return handler.handle(command);
|
||||
});
|
||||
|
||||
return future::join;
|
||||
}
|
||||
}
|
||||
+46
@@ -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.command.implementation;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.command.core.Command;
|
||||
import org.apache.fineract.command.core.CommandExecutor;
|
||||
import org.apache.fineract.command.core.CommandPipeline;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
@ConditionalOnBean(CommandPipeline.class)
|
||||
public class DefaultCommandPipeline implements CommandPipeline {
|
||||
|
||||
private final CommandExecutor executor;
|
||||
|
||||
@Override
|
||||
public <REQ, RES> Supplier<RES> send(final Command<REQ> command) {
|
||||
requireNonNull(command, "Command must not be null");
|
||||
|
||||
return executor.execute(command);
|
||||
}
|
||||
}
|
||||
+50
@@ -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.command.implementation;
|
||||
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.command.core.Command;
|
||||
import org.apache.fineract.command.core.CommandHandler;
|
||||
import org.apache.fineract.command.core.CommandRouter;
|
||||
import org.apache.fineract.command.core.exception.CommandHandlerNotFoundException;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
@ConditionalOnBean(CommandRouter.class)
|
||||
@SuppressWarnings({ "unchecked", "raw" })
|
||||
public class DefaultCommandRouter implements CommandRouter {
|
||||
|
||||
private final List<CommandHandler> commandHandlers;
|
||||
|
||||
@Override
|
||||
public <REQ, RES> CommandHandler<REQ, RES> route(Command<REQ> command) {
|
||||
// TODO: make sure there are no duplicate handlers
|
||||
if (command == null) {
|
||||
throw new CommandHandlerNotFoundException(command);
|
||||
}
|
||||
|
||||
return commandHandlers.stream().filter(handler -> handler.matches(command)).findFirst()
|
||||
.orElseThrow(() -> new CommandHandlerNotFoundException(command));
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* 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.command.implementation;
|
||||
|
||||
import com.lmax.disruptor.EventHandler;
|
||||
import com.lmax.disruptor.dsl.Disruptor;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Supplier;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.command.core.Command;
|
||||
import org.apache.fineract.command.core.CommandExecutor;
|
||||
import org.apache.fineract.command.core.CommandMiddleware;
|
||||
import org.apache.fineract.command.core.CommandRouter;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
@ConditionalOnProperty(value = "fineract.command.executor", havingValue = "disruptor")
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public class DisruptorCommandExecutor implements CommandExecutor, Closeable {
|
||||
|
||||
private final Disruptor<CommandEvent> disruptor;
|
||||
|
||||
@Override
|
||||
public <REQ, RES> Supplier<RES> execute(Command<REQ> command) {
|
||||
CommandEvent<REQ, RES> processedEvent = next(command);
|
||||
|
||||
return processedEvent.getFuture()::join;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
disruptor.shutdown();
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
private <REQ, RES> CommandEvent<REQ, RES> next(Command<REQ> command) {
|
||||
var ringBuffer = disruptor.getRingBuffer();
|
||||
|
||||
var sequenceId = ringBuffer.next();
|
||||
|
||||
CommandEvent<REQ, RES> event = ringBuffer.get(sequenceId);
|
||||
event.setCommand(command);
|
||||
ringBuffer.publish(sequenceId);
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class CommandEvent<REQ, RES> {
|
||||
|
||||
private Command<REQ> command;
|
||||
private CompletableFuture<RES> future = new CompletableFuture<>();
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public static class CompleteableCommandEventHandler implements EventHandler<CommandEvent> {
|
||||
|
||||
private final List<CommandMiddleware> middlewares;
|
||||
|
||||
private final CommandRouter router;
|
||||
|
||||
@Override
|
||||
public void onEvent(CommandEvent event, long sequence, boolean endOfBatch) throws Exception {
|
||||
try {
|
||||
for (CommandMiddleware middleware : middlewares) {
|
||||
middleware.invoke(event.getCommand());
|
||||
}
|
||||
|
||||
var handler = router.route(event.getCommand());
|
||||
|
||||
event.getFuture().complete(handler.handle(event.getCommand()));
|
||||
} catch (Exception e) {
|
||||
event.getFuture().completeExceptionally(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 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.command.implementation;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.command.core.Command;
|
||||
import org.apache.fineract.command.core.CommandExecutor;
|
||||
import org.apache.fineract.command.core.CommandHandler;
|
||||
import org.apache.fineract.command.core.CommandMiddleware;
|
||||
import org.apache.fineract.command.core.CommandRouter;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
@ConditionalOnProperty(value = "fineract.command.executor", havingValue = "sync")
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
public class SynchronousCommandExecutor implements CommandExecutor {
|
||||
|
||||
private final List<CommandMiddleware> middlewares;
|
||||
|
||||
private final CommandRouter router;
|
||||
|
||||
@Override
|
||||
public <REQ, RES> Supplier<RES> execute(Command<REQ> command) {
|
||||
for (CommandMiddleware middleware : middlewares) {
|
||||
middleware.invoke(command);
|
||||
}
|
||||
|
||||
CommandHandler<REQ, RES> handler = router.route(command);
|
||||
|
||||
return () -> handler.handle(command);
|
||||
}
|
||||
}
|
||||
+25
@@ -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.command.persistence;
|
||||
|
||||
import org.apache.fineract.command.persistence.model.CommandEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
|
||||
public interface CommandRepository extends JpaRepository<CommandEntity, Long>, JpaSpecificationExecutor<CommandEntity> {}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 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.command.persistence.converter;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.persistence.AttributeConverter;
|
||||
import jakarta.persistence.Converter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Converter
|
||||
public class JsonAttributeConverter implements AttributeConverter<JsonNode, String> {
|
||||
// TODO: it would be nicer to use a native JSON type on the database side, but not every system supports this;
|
||||
// string/text are the lowest common denominator that should work on every database
|
||||
|
||||
private final ObjectMapper mapper;
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public String convertToDatabaseColumn(JsonNode source) {
|
||||
if (source != null) {
|
||||
return mapper.writeValueAsString(source);
|
||||
}
|
||||
|
||||
// TODO: throw exception?
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public JsonNode convertToEntityAttribute(String source) {
|
||||
if (source != null) {
|
||||
return mapper.readTree(source);
|
||||
}
|
||||
|
||||
// TODO: throw exception?
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 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.command.persistence.mapping;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.fasterxml.jackson.databind.node.TextNode;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
public final class CommandJsonMapper {
|
||||
|
||||
private static final String CLASS_ATTRIBUTE = "@class";
|
||||
private final ObjectMapper mapper;
|
||||
|
||||
public <T> T map(JsonNode source) {
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var canonicalName = source.get(CLASS_ATTRIBUTE).asText();
|
||||
|
||||
try {
|
||||
return (T) mapper.convertValue(source, Class.forName(canonicalName));
|
||||
} catch (Exception e) {
|
||||
log.error("Error while mapping json node", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public JsonNode map(Object source) {
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var json = mapper.convertValue(source, ObjectNode.class);
|
||||
|
||||
json.set(CLASS_ATTRIBUTE, new TextNode(source.getClass().getCanonicalName()));
|
||||
|
||||
return json;
|
||||
}
|
||||
}
|
||||
+43
@@ -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.command.persistence.mapping;
|
||||
|
||||
import static org.mapstruct.InjectionStrategy.CONSTRUCTOR;
|
||||
import static org.mapstruct.MappingConstants.ComponentModel.SPRING;
|
||||
|
||||
import org.apache.fineract.command.core.Command;
|
||||
import org.apache.fineract.command.persistence.model.CommandEntity;
|
||||
import org.mapstruct.InheritInverseConfiguration;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
|
||||
@Mapper(componentModel = SPRING, injectionStrategy = CONSTRUCTOR, uses = { CommandJsonMapper.class })
|
||||
public interface CommandMapper {
|
||||
|
||||
@Mapping(ignore = true, target = "id")
|
||||
@Mapping(source = "id", target = "commandId")
|
||||
@Mapping(source = "createdAt", target = "createdAt")
|
||||
@Mapping(source = "tenantId", target = "tenantId")
|
||||
@Mapping(source = "username", target = "username")
|
||||
@Mapping(source = "payload", target = "payload")
|
||||
CommandEntity map(Command source);
|
||||
|
||||
@InheritInverseConfiguration
|
||||
Command map(CommandEntity source);
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* 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.command.persistence.model;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Convert;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.PostLoad;
|
||||
import jakarta.persistence.PrePersist;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.Transient;
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.UUID;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
import org.apache.fineract.command.persistence.converter.JsonAttributeConverter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@FieldNameConstants
|
||||
@Entity
|
||||
@Table(name = "m_command")
|
||||
public class CommandEntity implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@jakarta.persistence.Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "id")
|
||||
private Long id;
|
||||
|
||||
@Transient
|
||||
@Setter(value = AccessLevel.NONE)
|
||||
private boolean isNew = true;
|
||||
|
||||
@Column(name = "command_id")
|
||||
private UUID commandId;
|
||||
|
||||
@Column(name = "created_at")
|
||||
private OffsetDateTime createdAt;
|
||||
|
||||
@Column(name = "tenant_id")
|
||||
private String tenantId;
|
||||
|
||||
@Column(name = "username")
|
||||
private String username;
|
||||
|
||||
@Column(name = "payload")
|
||||
@Convert(converter = JsonAttributeConverter.class)
|
||||
private JsonNode payload;
|
||||
|
||||
@PrePersist
|
||||
@PostLoad
|
||||
void markNotNew() {
|
||||
this.isNew = false;
|
||||
}
|
||||
}
|
||||
+26
@@ -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.
|
||||
*/
|
||||
package org.apache.fineract.command.starter;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
@AutoConfiguration
|
||||
@Import({ CommandConfiguration.class, CommandPersistenceConfiguration.class })
|
||||
public class CommandAutoConfiguration {}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* 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.command.starter;
|
||||
|
||||
import com.lmax.disruptor.IgnoreExceptionHandler;
|
||||
import com.lmax.disruptor.WaitStrategy;
|
||||
import com.lmax.disruptor.YieldingWaitStrategy;
|
||||
import com.lmax.disruptor.dsl.Disruptor;
|
||||
import com.lmax.disruptor.util.DaemonThreadFactory;
|
||||
import java.util.List;
|
||||
import org.apache.fineract.command.core.CommandMiddleware;
|
||||
import org.apache.fineract.command.core.CommandProperties;
|
||||
import org.apache.fineract.command.core.CommandRouter;
|
||||
import org.apache.fineract.command.implementation.DisruptorCommandExecutor;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(CommandProperties.class)
|
||||
@ComponentScan("org.apache.fineract.command.core")
|
||||
@ComponentScan("org.apache.fineract.command.implementation")
|
||||
class CommandConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
WaitStrategy waitStrategy() {
|
||||
return new YieldingWaitStrategy();
|
||||
}
|
||||
|
||||
@Bean
|
||||
Disruptor<?> disruptor(CommandProperties properties, WaitStrategy waitStrategy, List<CommandMiddleware> middlewares,
|
||||
CommandRouter router) {
|
||||
// TODO: make this more configurable
|
||||
|
||||
// Create the disruptor
|
||||
Disruptor<DisruptorCommandExecutor.CommandEvent> disruptor = new Disruptor<>(DisruptorCommandExecutor.CommandEvent::new,
|
||||
properties.getRingBufferSize(), DaemonThreadFactory.INSTANCE, properties.getProducerType(), waitStrategy);
|
||||
|
||||
disruptor.handleEventsWith(new DisruptorCommandExecutor.CompleteableCommandEventHandler(middlewares, router));
|
||||
disruptor.setDefaultExceptionHandler(new IgnoreExceptionHandler());
|
||||
|
||||
// Start the disruptor
|
||||
disruptor.start();
|
||||
|
||||
return disruptor;
|
||||
}
|
||||
}
|
||||
+26
@@ -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.
|
||||
*/
|
||||
package org.apache.fineract.command.starter;
|
||||
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@ComponentScan("org.apache.fineract.command.persistence")
|
||||
class CommandPersistenceConfiguration {}
|
||||
+1
@@ -0,0 +1 @@
|
||||
org.apache.fineract.command.starter.CommandAutoConfiguration
|
||||
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
-->
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||
<changeSet author="fineract" id="command-00001" context="postgresql">
|
||||
<createTable tableName="m_command">
|
||||
<column autoIncrement="true" name="id" type="bigint" remarks="Internal ID">
|
||||
<constraints nullable="false" primaryKey="true"/>
|
||||
</column>
|
||||
<column name="created_at" type="timestamp with time zone" />
|
||||
<column name="command_id" type="uuid" />
|
||||
<column name="tenant_id" type="varchar(255)" />
|
||||
<column name="username" type="varchar(255)" />
|
||||
<column name="payload" type="text" />
|
||||
</createTable>
|
||||
</changeSet>
|
||||
<changeSet author="fineract" id="command-00002" context="mysql">
|
||||
<createTable tableName="command">
|
||||
<column autoIncrement="true" name="id" type="bigint" remarks="Internal ID">
|
||||
<constraints nullable="false" primaryKey="true"/>
|
||||
</column>
|
||||
<column name="created_at" type="datetime" />
|
||||
<column name="command_id" type="uuid" />
|
||||
<column name="tenant_id" type="varchar(255)" />
|
||||
<column name="username" type="varchar(255)" />
|
||||
<column name="payload" type="text" />
|
||||
</createTable>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 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.command;
|
||||
|
||||
import java.util.Map;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.command.persistence.CommandRepository;
|
||||
import org.apache.fineract.command.persistence.mapping.CommandMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.DynamicPropertyRegistry;
|
||||
import org.springframework.test.context.DynamicPropertySource;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.Network;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
|
||||
@Slf4j
|
||||
@SpringBootTest
|
||||
@ContextConfiguration(classes = TestConfiguration.class)
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
abstract class CommandBaseTest {
|
||||
|
||||
protected static Network network = Network.newNetwork();
|
||||
|
||||
@Container
|
||||
protected static final GenericContainer POSTGRES_CONTAINER = new GenericContainer("postgres:16").withNetwork(network)
|
||||
.withNetworkAliases("postgres").withExposedPorts(5432)
|
||||
.withEnv(Map.of("POSTGRES_DB", "fineract-test", "POSTGRES_USER", "root", "POSTGRES_PASSWORD", "mifos"));
|
||||
|
||||
@Autowired
|
||||
protected CommandRepository commandRepository;
|
||||
|
||||
@Autowired
|
||||
protected CommandMapper commandMapper;
|
||||
|
||||
@DynamicPropertySource
|
||||
protected static void configure(DynamicPropertyRegistry registry) {
|
||||
POSTGRES_CONTAINER.start();
|
||||
|
||||
registry.add("spring.datasource.driver-class-name", () -> "org.postgresql.Driver");
|
||||
registry.add("spring.datasource.username", () -> "root");
|
||||
registry.add("spring.datasource.password", () -> "mifos");
|
||||
registry.add("spring.datasource.url", () -> "jdbc:postgresql://" + POSTGRES_CONTAINER.getHost() + ":"
|
||||
+ POSTGRES_CONTAINER.getMappedPort(5432) + "/fineract-test");
|
||||
registry.add("spring.datasource.platform", () -> "postgresql");
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* 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.command;
|
||||
|
||||
import java.util.UUID;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.command.sample.command.DummyCommand;
|
||||
import org.apache.fineract.command.sample.data.DummyRequest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
@Slf4j
|
||||
@SpringBootTest
|
||||
@ContextConfiguration(classes = TestConfiguration.class)
|
||||
class CommandPersistenceTest extends CommandBaseTest {
|
||||
|
||||
@Test
|
||||
void save() {
|
||||
var content = "hello";
|
||||
var command = new DummyCommand();
|
||||
command.setId(UUID.randomUUID());
|
||||
command.setPayload(DummyRequest.builder().content(content).build());
|
||||
|
||||
var commandEntity = commandMapper.map(command);
|
||||
|
||||
var result = commandRepository.save(commandEntity);
|
||||
|
||||
log.info("Saved command: {}", result);
|
||||
|
||||
var found = commandRepository.findById(result.getId());
|
||||
|
||||
log.info("Found command: {}", found.orElse(null));
|
||||
}
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* 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.command;
|
||||
|
||||
import com.lmax.disruptor.YieldingWaitStrategy;
|
||||
import com.lmax.disruptor.dsl.Disruptor;
|
||||
import com.lmax.disruptor.dsl.ProducerType;
|
||||
import com.lmax.disruptor.util.DaemonThreadFactory;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Supplier;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.command.core.CommandPipeline;
|
||||
import org.apache.fineract.command.core.CommandRouter;
|
||||
import org.apache.fineract.command.implementation.DefaultCommandPipeline;
|
||||
import org.apache.fineract.command.implementation.DefaultCommandRouter;
|
||||
import org.apache.fineract.command.implementation.DisruptorCommandExecutor;
|
||||
import org.apache.fineract.command.sample.command.DummyCommand;
|
||||
import org.apache.fineract.command.sample.data.DummyRequest;
|
||||
import org.apache.fineract.command.sample.data.DummyResponse;
|
||||
import org.apache.fineract.command.sample.handler.DummyCommandHandler;
|
||||
import org.apache.fineract.command.sample.middleware.DummyIdempotencyMiddleware;
|
||||
import org.apache.fineract.command.sample.middleware.DummyMiddleware;
|
||||
import org.apache.fineract.command.sample.service.DefaultDummyService;
|
||||
import org.apache.fineract.command.sample.service.DefaultDummyTenantService;
|
||||
import org.openjdk.jmh.annotations.Benchmark;
|
||||
import org.openjdk.jmh.annotations.BenchmarkMode;
|
||||
import org.openjdk.jmh.annotations.Level;
|
||||
import org.openjdk.jmh.annotations.Mode;
|
||||
import org.openjdk.jmh.annotations.OutputTimeUnit;
|
||||
import org.openjdk.jmh.annotations.Scope;
|
||||
import org.openjdk.jmh.annotations.Setup;
|
||||
import org.openjdk.jmh.annotations.State;
|
||||
import org.openjdk.jmh.annotations.TearDown;
|
||||
|
||||
@Slf4j
|
||||
@BenchmarkMode(Mode.Throughput) // Measures operations per second
|
||||
@State(Scope.Benchmark) // Benchmark state for each thread
|
||||
@OutputTimeUnit(TimeUnit.SECONDS) // Output results in seconds
|
||||
@SuppressWarnings({ "raw" })
|
||||
public class CommandPipelineBenchmark {
|
||||
|
||||
private CommandRouter router;
|
||||
private Disruptor<DisruptorCommandExecutor.CommandEvent> disruptor;
|
||||
|
||||
private CommandPipeline pipeline;
|
||||
|
||||
@Setup(Level.Iteration)
|
||||
public void setUp() {
|
||||
this.router = new DefaultCommandRouter(List.of(new DummyCommandHandler(new DefaultDummyService(new DefaultDummyTenantService()))));
|
||||
|
||||
// Create the disruptor
|
||||
this.disruptor = new Disruptor<>(DisruptorCommandExecutor.CommandEvent::new, 2048, DaemonThreadFactory.INSTANCE, ProducerType.MULTI,
|
||||
new YieldingWaitStrategy());
|
||||
|
||||
disruptor.handleEventsWith(new DisruptorCommandExecutor.CompleteableCommandEventHandler(
|
||||
List.of(new DummyMiddleware(), new DummyIdempotencyMiddleware()), router));
|
||||
|
||||
// Start the disruptor
|
||||
disruptor.start();
|
||||
|
||||
pipeline = new DefaultCommandPipeline(new DisruptorCommandExecutor(disruptor));
|
||||
}
|
||||
|
||||
@TearDown(Level.Iteration)
|
||||
@SneakyThrows
|
||||
public void tearDown() {
|
||||
disruptor.shutdown(1, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public void processCommand() {
|
||||
var command = new DummyCommand();
|
||||
command.setId(UUID.randomUUID());
|
||||
command.setPayload(DummyRequest.builder().content("hello").build());
|
||||
|
||||
Supplier<DummyResponse> result = pipeline.send(command);
|
||||
|
||||
// NOTE: force yield
|
||||
result.get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* 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.command;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.command.core.CommandPipeline;
|
||||
import org.apache.fineract.command.sample.command.DummyCommand;
|
||||
import org.apache.fineract.command.sample.data.DummyRequest;
|
||||
import org.apache.fineract.command.sample.data.DummyResponse;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
@Slf4j
|
||||
@SpringBootTest
|
||||
@ContextConfiguration(classes = TestConfiguration.class)
|
||||
class CommandPipelineTest extends CommandBaseTest {
|
||||
|
||||
@Autowired
|
||||
private CommandPipeline pipeline;
|
||||
|
||||
@Test
|
||||
void processCommand() {
|
||||
var content = "hello";
|
||||
var command = new DummyCommand();
|
||||
command.setId(UUID.randomUUID());
|
||||
command.setPayload(DummyRequest.builder().content(content).build());
|
||||
|
||||
var result = pipeline.send(command);
|
||||
|
||||
assertNotNull(result, "Response should not be null.");
|
||||
|
||||
Object response = result.get();
|
||||
|
||||
assertNotNull(response, "Response should not be null.");
|
||||
|
||||
assertInstanceOf(DummyResponse.class, response, "Response is of wrong type.");
|
||||
|
||||
if (response instanceof DummyResponse dummyResponse) {
|
||||
log.warn("Result: {}", dummyResponse);
|
||||
|
||||
assertNotNull(dummyResponse.getContent(), "Response body should not be null.");
|
||||
assertNotNull(dummyResponse.getRequestId(), "Request ID should not be null.");
|
||||
assertEquals(content.toUpperCase(Locale.ROOT), dummyResponse.getContent(), "Wrong response content.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* 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.command;
|
||||
|
||||
import static org.apache.fineract.command.core.CommandConstants.COMMAND_REQUEST_ID;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.springframework.http.HttpHeaders.ACCEPT;
|
||||
import static org.springframework.http.HttpHeaders.CONTENT_TYPE;
|
||||
import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
import static org.springframework.http.HttpStatus.OK;
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
|
||||
import static org.springframework.http.MediaType.APPLICATION_PROBLEM_JSON_VALUE;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.command.sample.data.DummyRequest;
|
||||
import org.apache.fineract.command.sample.data.DummyResponse;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.boot.test.web.server.LocalServerPort;
|
||||
import org.springframework.http.ProblemDetail;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
@Slf4j
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@ContextConfiguration(classes = TestConfiguration.class)
|
||||
@SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert")
|
||||
class CommandSampleApiTest extends CommandBaseTest {
|
||||
|
||||
@LocalServerPort
|
||||
private int port;
|
||||
|
||||
private String baseUrl;
|
||||
|
||||
private List<ClientHttpRequestInterceptor> interceptors;
|
||||
|
||||
@Autowired
|
||||
private TestRestTemplate restTemplate;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
this.baseUrl = "http://localhost:" + port + "/test/dummy";
|
||||
this.interceptors = Collections.singletonList((request, body, execution) -> {
|
||||
var headers = request.getHeaders();
|
||||
headers.add(COMMAND_REQUEST_ID, UUID.randomUUID().toString());
|
||||
headers.add("x-fineract-tenant-id", "dummy");
|
||||
headers.add(CONTENT_TYPE, APPLICATION_JSON_VALUE);
|
||||
headers.addAll(ACCEPT, List.of(APPLICATION_JSON_VALUE, APPLICATION_PROBLEM_JSON_VALUE));
|
||||
return execution.execute(request, body);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void validation() {
|
||||
restTemplate.getRestTemplate().setInterceptors(interceptors);
|
||||
var problemDetail = restTemplate.postForObject(baseUrl + "/sync", DummyRequest.builder().build(), ProblemDetail.class);
|
||||
|
||||
log.warn("Problem detail (sync) : {} ({})", problemDetail.getDetail(), problemDetail.getProperties());
|
||||
|
||||
assertNotNull(problemDetail, "Response should not be null.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void dummyApiSync() {
|
||||
var content = "test-sync";
|
||||
|
||||
restTemplate.getRestTemplate().setInterceptors(interceptors);
|
||||
var result = restTemplate.postForObject(baseUrl + "/sync", DummyRequest.builder().content(content).build(), DummyResponse.class);
|
||||
|
||||
log.warn("Result (sync) : {} ({})", result.getContent(), result.getRequestId());
|
||||
|
||||
assertNotNull(result, "Response should not be null.");
|
||||
assertNotNull(result.getContent(), "Response body should not be null.");
|
||||
assertNotNull(result.getRequestId(), "Request ID should not be null.");
|
||||
assertNotNull(result.getTenantId(), "Tenant ID should not be null.");
|
||||
assertEquals("dummy", result.getTenantId(), "Unexpected tenant ID.");
|
||||
assertEquals(content.toUpperCase(Locale.ROOT), result.getContent(), "Wrong response content.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void dummyApiAsync() {
|
||||
var content = "test-async";
|
||||
|
||||
restTemplate.getRestTemplate().setInterceptors(interceptors);
|
||||
var result = restTemplate.postForObject(baseUrl + "/async", DummyRequest.builder().content(content).build(), DummyResponse.class);
|
||||
|
||||
log.warn("Result (async): {} ({})", result.getContent(), result.getRequestId());
|
||||
|
||||
assertNotNull(result, "Response should not be null.");
|
||||
assertNotNull(result.getContent(), "Response body should not be null.");
|
||||
assertNotNull(result.getRequestId(), "Request ID should not be null.");
|
||||
// TODO: make sure all ThreadLocal variables are available
|
||||
// assertNotNull(result.getTenantId(), "Tenant ID should not be null.");
|
||||
// assertEquals("dummy", result.getTenantId(), "Unexpected tenant ID.");
|
||||
assertEquals(content.toUpperCase(Locale.ROOT), result.getContent(), "Wrong response content.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void dummyApiIdempotencyAsync() {
|
||||
dummyApiIdempotency("/async");
|
||||
}
|
||||
|
||||
@Test
|
||||
void dummyApiIdempotencySync() {
|
||||
dummyApiIdempotency("/sync");
|
||||
}
|
||||
|
||||
void dummyApiIdempotency(String path) {
|
||||
var id = UUID.randomUUID().toString();
|
||||
var content = "test-idempotency";
|
||||
var request = DummyRequest.builder().content(content).build();
|
||||
List<ClientHttpRequestInterceptor> interceptors = Collections.singletonList((req, body, execution) -> {
|
||||
var headers = req.getHeaders();
|
||||
headers.add(COMMAND_REQUEST_ID, id);
|
||||
headers.add("x-fineract-tenant-id", "dummy");
|
||||
headers.add(CONTENT_TYPE, APPLICATION_JSON_VALUE);
|
||||
headers.addAll(ACCEPT, List.of(APPLICATION_JSON_VALUE, APPLICATION_PROBLEM_JSON_VALUE));
|
||||
return execution.execute(req, body);
|
||||
});
|
||||
|
||||
restTemplate.getRestTemplate().setInterceptors(interceptors);
|
||||
|
||||
// first request passes
|
||||
var response = restTemplate.postForEntity(baseUrl + path, request, Map.class);
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(OK);
|
||||
|
||||
// second request fails, because we are using the same request ID in both cases
|
||||
response = restTemplate.postForEntity(baseUrl + path, request, Map.class);
|
||||
|
||||
log.warn("Body: {} - {}", response.getBody(), response.getStatusCode());
|
||||
|
||||
// Assert HTTP status
|
||||
assertThat(response.getStatusCode()).isEqualTo(INTERNAL_SERVER_ERROR);
|
||||
|
||||
log.info("Idempotency all good!");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* 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.command;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.command.core.CommandProperties;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.validation.Validator;
|
||||
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
||||
import yakworks.i18n.icu.ICUBundleMessageSource;
|
||||
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@EnableConfigurationProperties({ CommandProperties.class, JpaProperties.class })
|
||||
@EnableAutoConfiguration
|
||||
@EnableJpaRepositories(basePackages = { "org.apache.fineract.command.persistence" })
|
||||
@EnableAsync
|
||||
@PropertySource("classpath:application-test.properties")
|
||||
@ComponentScan("org.apache.fineract.command.sample")
|
||||
public class TestConfiguration {
|
||||
|
||||
@Bean
|
||||
MessageSource messageSource() {
|
||||
var messageSource = new ICUBundleMessageSource();
|
||||
messageSource.setBasenames("classpath:org.apache.fineract.messages");
|
||||
messageSource.setDefaultEncoding("UTF-8");
|
||||
|
||||
return messageSource;
|
||||
}
|
||||
|
||||
@Bean
|
||||
Validator validatorFactoryBean(MessageSource messageSource) {
|
||||
var localValidatorFactoryBean = new LocalValidatorFactoryBean();
|
||||
localValidatorFactoryBean.setValidationMessageSource(messageSource);
|
||||
|
||||
return localValidatorFactoryBean;
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* 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.command.sample.api;
|
||||
|
||||
import static org.apache.fineract.command.core.CommandConstants.COMMAND_REQUEST_ID;
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
|
||||
import static org.springframework.http.MediaType.APPLICATION_PROBLEM_JSON_VALUE;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Supplier;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.command.core.CommandPipeline;
|
||||
import org.apache.fineract.command.sample.command.DummyCommand;
|
||||
import org.apache.fineract.command.sample.data.DummyRequest;
|
||||
import org.apache.fineract.command.sample.data.DummyResponse;
|
||||
import org.apache.fineract.command.sample.service.DefaultDummyTenantService;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping(value = "/test/dummy", consumes = APPLICATION_JSON_VALUE, produces = { APPLICATION_JSON_VALUE,
|
||||
APPLICATION_PROBLEM_JSON_VALUE })
|
||||
class DummyApiController {
|
||||
// "application/vnd.fineract+json;charset=UTF-8;version=1.0"
|
||||
|
||||
private final DefaultDummyTenantService tenantService;
|
||||
|
||||
private final CommandPipeline pipeline;
|
||||
|
||||
@PostMapping("/sync")
|
||||
DummyResponse dummySync(@RequestHeader(value = COMMAND_REQUEST_ID, required = false) UUID requestId,
|
||||
@RequestHeader(value = "x-fineract-tenant-id", required = false) String tenantId, @Valid @RequestBody DummyRequest request) {
|
||||
var command = new DummyCommand();
|
||||
command.setId(requestId);
|
||||
command.setPayload(request);
|
||||
command.setCreatedAt(OffsetDateTime.now(ZoneId.of("UTC")));
|
||||
|
||||
tenantService.set(tenantId);
|
||||
|
||||
Supplier<DummyResponse> result = pipeline.send(command);
|
||||
|
||||
return result.get();
|
||||
}
|
||||
|
||||
@Async
|
||||
@PostMapping("/async")
|
||||
CompletableFuture<DummyResponse> dummyAsync(@RequestHeader(value = COMMAND_REQUEST_ID, required = false) UUID requestId,
|
||||
@RequestHeader(value = "x-fineract-tenant-id", required = false) String tenantId, @RequestBody DummyRequest request) {
|
||||
var command = new DummyCommand();
|
||||
command.setId(requestId);
|
||||
command.setPayload(request);
|
||||
|
||||
tenantService.set(tenantId);
|
||||
|
||||
Supplier<DummyResponse> result = pipeline.send(command);
|
||||
|
||||
return CompletableFuture.supplyAsync(result);
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* 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.command.sample.api;
|
||||
|
||||
import static org.springframework.http.MediaType.APPLICATION_PROBLEM_JSON;
|
||||
|
||||
import jakarta.validation.ConstraintViolation;
|
||||
import jakarta.validation.ConstraintViolationException;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.command.sample.data.DummyError;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ProblemDetail;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.bind.support.WebExchangeBindException;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@RestControllerAdvice
|
||||
public class DummyExceptionHandler {
|
||||
|
||||
private final MessageSource messageSource;
|
||||
|
||||
@ExceptionHandler(Throwable.class)
|
||||
public ResponseEntity<ProblemDetail> handleRuntimeException(Throwable ex) {
|
||||
var problemDetails = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, ex.getLocalizedMessage());
|
||||
problemDetails.setType(URI.create("https://fineract.apache.org/validation"));
|
||||
problemDetails.setTitle(messageSource.getMessage("org.apache.fineract.common.validation.error", null, Locale.US)); // TODO:
|
||||
// check
|
||||
// this,
|
||||
// get
|
||||
// locale
|
||||
|
||||
List<DummyError> errors = new ArrayList<>();
|
||||
|
||||
if (ex instanceof ConstraintViolationException cve) {
|
||||
for (ConstraintViolation<?> violation : cve.getConstraintViolations()) {
|
||||
errors.add(new DummyError(violation.getPropertyPath().toString(), violation.getMessage(), violation.getMessageTemplate()));
|
||||
}
|
||||
} else if (ex instanceof WebExchangeBindException webe) {
|
||||
for (FieldError fieldError : webe.getFieldErrors()) {
|
||||
errors.add(new DummyError(fieldError.getField(), fieldError.getDefaultMessage(), fieldError.getCode()));
|
||||
}
|
||||
} else if (ex instanceof MethodArgumentNotValidException manve) {
|
||||
for (FieldError fieldError : manve.getBindingResult().getFieldErrors()) {
|
||||
errors.add(new DummyError(fieldError.getField(), fieldError.getDefaultMessage(), fieldError.getCode()));
|
||||
}
|
||||
}
|
||||
|
||||
if (!errors.isEmpty()) {
|
||||
problemDetails.setProperty("errors", errors);
|
||||
}
|
||||
|
||||
return ResponseEntity.badRequest().contentType(APPLICATION_PROBLEM_JSON).body(problemDetails);
|
||||
}
|
||||
}
|
||||
+28
@@ -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.command.sample.command;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.apache.fineract.command.core.Command;
|
||||
import org.apache.fineract.command.sample.data.DummyRequest;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class DummyCommand extends Command<DummyRequest> {}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* 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.command.sample.data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
|
||||
@Builder
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@FieldNameConstants
|
||||
public class DummyError implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private String field;
|
||||
|
||||
private String message;
|
||||
|
||||
private String code;
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* 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.command.sample.data;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.UUID;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
|
||||
@Builder
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@FieldNameConstants
|
||||
public class DummyRequest implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private UUID id;
|
||||
|
||||
@NotBlank(message = "{org.apache.fineract.dummy.request.content.not-empty}")
|
||||
private String content;
|
||||
}
|
||||
+47
@@ -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.command.sample.data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.UUID;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
|
||||
@Builder
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@FieldNameConstants
|
||||
public class DummyResponse implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private UUID requestId;
|
||||
|
||||
private String tenantId;
|
||||
|
||||
private String content;
|
||||
|
||||
private String error;
|
||||
}
|
||||
+41
@@ -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.command.sample.handler;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.command.core.Command;
|
||||
import org.apache.fineract.command.core.CommandHandler;
|
||||
import org.apache.fineract.command.sample.data.DummyRequest;
|
||||
import org.apache.fineract.command.sample.data.DummyResponse;
|
||||
import org.apache.fineract.command.sample.service.DummyService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
public class DummyCommandHandler implements CommandHandler<DummyRequest, DummyResponse> {
|
||||
|
||||
private final DummyService dummyService;
|
||||
|
||||
@Override
|
||||
public DummyResponse handle(Command<DummyRequest> command) {
|
||||
return dummyService.process(command.getPayload());
|
||||
}
|
||||
}
|
||||
+50
@@ -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.command.sample.middleware;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.command.core.Command;
|
||||
import org.apache.fineract.command.core.CommandMiddleware;
|
||||
import org.apache.fineract.command.core.exception.CommandIllegalArgumentException;
|
||||
import org.apache.fineract.command.sample.command.DummyCommand;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
public class DummyIdempotencyMiddleware implements CommandMiddleware {
|
||||
|
||||
// NOTE: in production you would use of course a database or Redis
|
||||
private static final List<UUID> IDS = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void invoke(Command<?> command) {
|
||||
if (command instanceof DummyCommand c) {
|
||||
if (IDS.contains(c.getId())) {
|
||||
throw new CommandIllegalArgumentException(c, "Duplicate request ID: " + c.getId());
|
||||
}
|
||||
|
||||
IDS.add(c.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
+39
@@ -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.
|
||||
*/
|
||||
package org.apache.fineract.command.sample.middleware;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.command.core.Command;
|
||||
import org.apache.fineract.command.core.CommandMiddleware;
|
||||
import org.apache.fineract.command.sample.command.DummyCommand;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
public class DummyMiddleware implements CommandMiddleware {
|
||||
|
||||
@Override
|
||||
public void invoke(Command<?> command) {
|
||||
if (command instanceof DummyCommand c) {
|
||||
c.getPayload().setId(command.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* 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.command.sample.service;
|
||||
|
||||
import java.util.Locale;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.command.sample.data.DummyRequest;
|
||||
import org.apache.fineract.command.sample.data.DummyResponse;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class DefaultDummyService implements DummyService {
|
||||
|
||||
private final DefaultDummyTenantService tenantService;
|
||||
|
||||
@Override
|
||||
public DummyResponse process(DummyRequest request) {
|
||||
return DummyResponse.builder().requestId(request.getId()).tenantId(tenantService.get())
|
||||
.content(request.getContent().toUpperCase(Locale.ROOT)).build();
|
||||
}
|
||||
}
|
||||
+35
@@ -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.command.sample.service;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class DefaultDummyTenantService {
|
||||
|
||||
private static final ThreadLocal<String> tenantHolder = new ThreadLocal<>();
|
||||
|
||||
public void set(String tenantId) {
|
||||
tenantHolder.set(tenantId);
|
||||
}
|
||||
|
||||
public String get() {
|
||||
return tenantHolder.get();
|
||||
}
|
||||
}
|
||||
+27
@@ -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.command.sample.service;
|
||||
|
||||
import org.apache.fineract.command.sample.data.DummyRequest;
|
||||
import org.apache.fineract.command.sample.data.DummyResponse;
|
||||
|
||||
public interface DummyService {
|
||||
|
||||
DummyResponse process(DummyRequest request);
|
||||
}
|
||||
@@ -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.
|
||||
#
|
||||
|
||||
#server.error.include-message=always
|
||||
#server.error.include-binding-errors=always
|
||||
#server.error.include-stacktrace=always
|
||||
debug=true
|
||||
|
||||
fineract.command.enabled=true
|
||||
fineract.command.executor=sync
|
||||
fineract.command.ring-buffer-size=1024
|
||||
fineract.command.producer-type=single
|
||||
|
||||
spring.liquibase.enabled=true
|
||||
spring.liquibase.drop-first=true
|
||||
spring.liquibase.default-schema=public
|
||||
spring.liquibase.contexts=postgresql
|
||||
spring.liquibase.change-log=classpath:/db/custom-changelog/0001_command_init.xml
|
||||
@@ -0,0 +1,71 @@
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
# common
|
||||
org.apache.fineract.common.validation.error=Validation Error
|
||||
org.apache.fineract.common.validation.AssertFalse.message=must be false
|
||||
org.apache.fineract.common.validation.AssertTrue.message=must be true
|
||||
org.apache.fineract.common.validation.DecimalMax.message=must be less than ${inclusive == true ? 'or equal to ' : ''}{value}
|
||||
org.apache.fineract.common.validation.DecimalMin.message=must be greater than ${inclusive == true ? 'or equal to ' : ''}{value}
|
||||
org.apache.fineract.common.validation.Digits.message=numeric value out of bounds (<{integer} digits>.<{fraction} digits> expected)
|
||||
org.apache.fineract.common.validation.Email.message=must be a well-formed email address
|
||||
org.apache.fineract.common.validation.Future.message=must be a future date
|
||||
org.apache.fineract.common.validation.FutureOrPresent.message=must be a date in the present or in the future
|
||||
org.apache.fineract.common.validation.Max.message=must be less than or equal to {value}
|
||||
org.apache.fineract.common.validation.Min.message=must be greater than or equal to {value}
|
||||
org.apache.fineract.common.validation.Negative.message=must be less than 0
|
||||
org.apache.fineract.common.validation.NegativeOrZero.message=must be less than or equal to 0
|
||||
org.apache.fineract.common.validation.NotBlank.message=must not be blank
|
||||
org.apache.fineract.common.validation.NotEmpty.message=must not be empty
|
||||
org.apache.fineract.common.validation.NotNull.message=must not be null
|
||||
org.apache.fineract.common.validation.Null.message=must be null
|
||||
org.apache.fineract.common.validation.Past.message=must be a past date
|
||||
org.apache.fineract.common.validation.PastOrPresent.message=must be a date in the past or in the present
|
||||
org.apache.fineract.common.validation.Pattern.message=must match "{regexp}"
|
||||
org.apache.fineract.common.validation.Positive.message=must be greater than 0
|
||||
org.apache.fineract.common.validation.PositiveOrZero.message=must be greater than or equal to 0
|
||||
org.apache.fineract.common.validation.Size.message=size must be between {min} and {max}
|
||||
org.apache.fineract.common.validation.CreditCardNumber.message=invalid credit card number
|
||||
org.apache.fineract.common.validation.Currency.message=invalid currency (must be one of {value})
|
||||
org.apache.fineract.common.validation.EAN.message=invalid {type} barcode
|
||||
org.apache.fineract.common.validation.ISBN.message=invalid ISBN
|
||||
org.apache.fineract.common.validation.Length.message=length must be between {min} and {max}
|
||||
org.apache.fineract.common.validation.CodePointLength.message=length must be between {min} and {max}
|
||||
org.apache.fineract.common.validation.LuhnCheck.message=the check digit for ${validatedValue} is invalid, Luhn Modulo 10 checksum failed
|
||||
org.apache.fineract.common.validation.Mod10Check.message=the check digit for ${validatedValue} is invalid, Modulo 10 checksum failed
|
||||
org.apache.fineract.common.validation.Mod11Check.message=the check digit for ${validatedValue} is invalid, Modulo 11 checksum failed
|
||||
org.apache.fineract.common.validation.ModCheck.message=the check digit for ${validatedValue} is invalid, {modType} checksum failed
|
||||
org.apache.fineract.common.validation.Normalized.message=must be normalized
|
||||
org.apache.fineract.common.validation.ParametersScriptAssert.message=script expression "{script}" didn't evaluate to true
|
||||
org.apache.fineract.common.validation.Range.message=must be between {min} and {max}
|
||||
org.apache.fineract.common.validation.ScriptAssert.message=script expression "{script}" didn't evaluate to true
|
||||
org.apache.fineract.common.validation.UniqueElements.message=must only contain unique elements
|
||||
org.apache.fineract.common.validation.URL.message=must be a valid URL
|
||||
org.apache.fineract.common.validation.UUID.message=must be a valid UUID
|
||||
org.apache.fineract.common.validation.br.CNPJ.message=invalid Brazilian corporate taxpayer registry number (CNPJ)
|
||||
org.apache.fineract.common.validation.br.CPF.message=invalid Brazilian individual taxpayer registry number (CPF)
|
||||
org.apache.fineract.common.validation.br.TituloEleitoral.message=invalid Brazilian Voter ID card number
|
||||
org.apache.fineract.common.validation.pl.REGON.message=invalid Polish Taxpayer Identification Number (REGON)
|
||||
org.apache.fineract.common.validation.pl.NIP.message=invalid VAT Identification Number (NIP)
|
||||
org.apache.fineract.common.validation.pl.PESEL.message=invalid Polish National Identification Number (PESEL)
|
||||
org.apache.fineract.common.validation.ru.INN.message=invalid Russian taxpayer identification number (INN)
|
||||
org.apache.fineract.common.validation.time.DurationMax.message=must be shorter than${inclusive == true ? ' or equal to' : ''}${days == 0 ? '' : days == 1 ? ' 1 day' : ' ' += days += ' days'}${hours == 0 ? '' : hours == 1 ? ' 1 hour' : ' ' += hours += ' hours'}${minutes == 0 ? '' : minutes == 1 ? ' 1 minute' : ' ' += minutes += ' minutes'}${seconds == 0 ? '' : seconds == 1 ? ' 1 second' : ' ' += seconds += ' seconds'}${millis == 0 ? '' : millis == 1 ? ' 1 milli' : ' ' += millis += ' millis'}${nanos == 0 ? '' : nanos == 1 ? ' 1 nano' : ' ' += nanos += ' nanos'}
|
||||
org.apache.fineract.common.validation.time.DurationMin.message=must be longer than${inclusive == true ? ' or equal to' : ''}${days == 0 ? '' : days == 1 ? ' 1 day' : ' ' += days += ' days'}${hours == 0 ? '' : hours == 1 ? ' 1 hour' : ' ' += hours += ' hours'}${minutes == 0 ? '' : minutes == 1 ? ' 1 minute' : ' ' += minutes += ' minutes'}${seconds == 0 ? '' : seconds == 1 ? ' 1 second' : ' ' += seconds += ' seconds'}${millis == 0 ? '' : millis == 1 ? ' 1 milli' : ' ' += millis += ' millis'}${nanos == 0 ? '' : nanos == 1 ? ' 1 nano' : ' ' += nanos += ' nanos'}
|
||||
# dummy
|
||||
org.apache.fineract.dummy.request.content.not-empty=Dummy request content must have a value
|
||||
@@ -0,0 +1,70 @@
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
# common
|
||||
org.apache.fineract.common.validation.error=Fehler
|
||||
org.apache.fineract.common.validation.AssertFalse.message=muss falsch sein
|
||||
org.apache.fineract.common.validation.AssertTrue.message=muss wahr sein
|
||||
org.apache.fineract.common.validation.DecimalMax.message=muss kleiner ${inclusive == true ? 'oder gleich ' : ''}{value} sein
|
||||
org.apache.fineract.common.validation.DecimalMin.message=muss gr\u00f6\u00dfer ${inclusive == true ? 'oder gleich ' : ''}{value} sein
|
||||
org.apache.fineract.common.validation.Digits.message=numerischer Wert au\u00dferhalb des g\u00fcltigen Bereichs (<{integer} digits>.<{fraction} digits> erwartet)
|
||||
org.apache.fineract.common.validation.Email.message=muss eine korrekt formatierte E-Mail-Adresse sein
|
||||
org.apache.fineract.common.validation.Future.message=muss ein Datum in der Zukunft sein
|
||||
org.apache.fineract.common.validation.FutureOrPresent.message=muss ein Datum in der Gegenwart oder in der Zukunft sein
|
||||
org.apache.fineract.common.validation.Max.message=muss kleiner-gleich {value} sein
|
||||
org.apache.fineract.common.validation.Min.message=muss gr\u00f6\u00dfer-gleich {value} sein
|
||||
org.apache.fineract.common.validation.Negative.message=muss kleiner als 0 sein
|
||||
org.apache.fineract.common.validation.NegativeOrZero.message=muss kleiner-gleich 0 sein
|
||||
org.apache.fineract.common.validation.NotBlank.message=darf nicht leer sein
|
||||
org.apache.fineract.common.validation.NotEmpty.message=darf nicht leer sein
|
||||
org.apache.fineract.common.validation.NotNull.message=darf nicht null sein
|
||||
org.apache.fineract.common.validation.Null.message=muss null sein
|
||||
org.apache.fineract.common.validation.Past.message=muss ein Datum in der Vergangenheit sein
|
||||
org.apache.fineract.common.validation.PastOrPresent.message=muss ein Datum in der Vergangenheit oder in der Gegenwart sein
|
||||
org.apache.fineract.common.validation.Pattern.message=muss mit "{regexp}" \u00fcbereinstimmen
|
||||
org.apache.fineract.common.validation.Positive.message=muss gr\u00f6\u00dfer als 0 sein
|
||||
org.apache.fineract.common.validation.PositiveOrZero.message=muss gr\u00f6\u00dfer-gleich 0 sein
|
||||
org.apache.fineract.common.validation.Size.message=Gr\u00f6\u00dfe muss zwischen {min} und {max} sein
|
||||
org.apache.fineract.common.validation.CreditCardNumber.message=ung\u00fcltige Kreditkartennummer
|
||||
org.apache.fineract.common.validation.Currency.message=ung\u00fcltige W\u00e4hrung (muss eine der folgenden sein: {value})
|
||||
org.apache.fineract.common.validation.EAN.message=ung\u00fcltiger {type}-Barcode
|
||||
org.apache.fineract.common.validation.ISBN.message=ung\u00fcltige ISBN
|
||||
org.apache.fineract.common.validation.Length.message=L\u00e4nge muss zwischen {min} und {max} sein
|
||||
org.apache.fineract.common.validation.CodePointLength.message=L\u00e4nge muss zwischen {min} und {max} sein
|
||||
org.apache.fineract.common.validation.LuhnCheck.message=die Pr\u00fcfziffer f\u00fcr ${validatedValue} ist ung\u00fcltig, Luhn Modulo 10-Kontrollsumme ist fehlgeschlagen
|
||||
org.apache.fineract.common.validation.Mod10Check.message=die Pr\u00fcfziffer f\u00fcr ${validatedValue} ist ung\u00fcltig, Modulo 10-Kontrollsumme ist fehlgeschlagen
|
||||
org.apache.fineract.common.validation.Mod11Check.message=die Pr\u00fcfziffer f\u00fcr ${validatedValue} ist ung\u00fcltig, Modulo 11-Kontrollsumme ist fehlgeschlagen
|
||||
org.apache.fineract.common.validation.ModCheck.message=die Pr\u00fcfziffer f\u00fcr ${validatedValue} ist ung\u00fcltig, {modType}-Kontrollsumme ist fehlgeschlagen
|
||||
org.apache.fineract.common.validation.ParametersScriptAssert.message=die Evaluierung des Scriptausdrucks "{script}" ergab nicht true
|
||||
org.apache.fineract.common.validation.Range.message=muss zwischen {min} und {max} sein
|
||||
org.apache.fineract.common.validation.ScriptAssert.message=die Evaluierung des Scriptausdrucks "{script}" ergab nicht true
|
||||
org.apache.fineract.common.validation.UniqueElements.message=darf nur eindeutige Elemente enthalten
|
||||
org.apache.fineract.common.validation.URL.message=muss eine g\u00fcltige URL sein
|
||||
org.apache.fineract.common.validation.UUID.message=muss eine g\u00fcltige UUID sein
|
||||
org.apache.fineract.common.validation.br.CNPJ.message=ung\u00fcltige brasilianische Registriernummer f\u00fcr K\u00f6rperschaftssteuerzahlungen (CNPJ)
|
||||
org.apache.fineract.common.validation.br.CPF.message=ung\u00fcltige brasilianische Registriernummer f\u00fcr Steuerzahlungen nat\u00fcrlicher Personen (CPF)
|
||||
org.apache.fineract.common.validation.br.TituloEleitoral.message=ung\u00fcltige brasilianische ID-Karte f\u00fcr Entscheidungsberechtigte
|
||||
org.apache.fineract.common.validation.pl.REGON.message=ung\u00fcltige polnische Steuerzahleridentifikationsnummer (REGON)
|
||||
org.apache.fineract.common.validation.pl.NIP.message=ung\u00fcltige Mehrwertsteueridentifikationsnummer (NIP)
|
||||
org.apache.fineract.common.validation.pl.PESEL.message=ung\u00fcltige polnische nationale Identifikationsnummer (PESEL)
|
||||
org.apache.fineract.common.validation.ru.INN.message=ung\u00fcltige russisch Steuerzahleridentifikationsnummer (INN)
|
||||
org.apache.fineract.common.validation.time.DurationMax.message=muss k\u00fcrzer sein als${inclusive == true ? ' oder gleich' : ''}${days == 0 ? '' : days == 1 ? ' 1 Tag' : ' ' += days += ' Tage'}${hours == 0 ? '' : hours == 1 ? ' 1 Stunde' : ' ' += hours += ' Stunden'}${minutes == 0 ? '' : minutes == 1 ? ' 1 Minute' : ' ' += minutes += ' Minuten'}${seconds == 0 ? '' : seconds == 1 ? ' 1 Sekunde' : ' ' += seconds += ' Sekunden'}${millis == 0 ? '' : millis == 1 ? ' 1 Millisekunde' : ' ' += millis += ' Millisekunden'}${nanos == 0 ? '' : nanos == 1 ? ' 1 Nanosekunde' : ' ' += nanos += ' Nanosekunden'}
|
||||
org.apache.fineract.common.validation.time.DurationMin.message=muss gr\u00f6\u00dfer sein als${inclusive == true ? ' oder gleich' : ''}${days == 0 ? '' : days == 1 ? ' 1 Tag' : ' ' += days += ' Tage'}${hours == 0 ? '' : hours == 1 ? ' 1 Stunde' : ' ' += hours += ' Stunden'}${minutes == 0 ? '' : minutes == 1 ? ' 1 Minute' : ' ' += minutes += ' Minuten'}${seconds == 0 ? '' : seconds == 1 ? ' 1 Sekunde' : ' ' += seconds += ' Sekunden'}${millis == 0 ? '' : millis == 1 ? ' 1 Millisekunde' : ' ' += millis += ' Millisekunden'}${nanos == 0 ? '' : nanos == 1 ? ' 1 Nanosekunde' : ' ' += nanos += ' Nanosekunden'}
|
||||
# dummy
|
||||
org.apache.fineract.dummy.request.content.not-empty=Dummy Request Attribut muss einen Wert enthalten
|
||||
@@ -0,0 +1,71 @@
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
# common
|
||||
org.apache.fineract.common.validation.error=Validation Error
|
||||
org.apache.fineract.common.validation.AssertFalse.message=must be false
|
||||
org.apache.fineract.common.validation.AssertTrue.message=must be true
|
||||
org.apache.fineract.common.validation.DecimalMax.message=must be less than ${inclusive == true ? 'or equal to ' : ''}{value}
|
||||
org.apache.fineract.common.validation.DecimalMin.message=must be greater than ${inclusive == true ? 'or equal to ' : ''}{value}
|
||||
org.apache.fineract.common.validation.Digits.message=numeric value out of bounds (<{integer} digits>.<{fraction} digits> expected)
|
||||
org.apache.fineract.common.validation.Email.message=must be a well-formed email address
|
||||
org.apache.fineract.common.validation.Future.message=must be a future date
|
||||
org.apache.fineract.common.validation.FutureOrPresent.message=must be a date in the present or in the future
|
||||
org.apache.fineract.common.validation.Max.message=must be less than or equal to {value}
|
||||
org.apache.fineract.common.validation.Min.message=must be greater than or equal to {value}
|
||||
org.apache.fineract.common.validation.Negative.message=must be less than 0
|
||||
org.apache.fineract.common.validation.NegativeOrZero.message=must be less than or equal to 0
|
||||
org.apache.fineract.common.validation.NotBlank.message=must not be blank
|
||||
org.apache.fineract.common.validation.NotEmpty.message=must not be empty
|
||||
org.apache.fineract.common.validation.NotNull.message=must not be null
|
||||
org.apache.fineract.common.validation.Null.message=must be null
|
||||
org.apache.fineract.common.validation.Past.message=must be a past date
|
||||
org.apache.fineract.common.validation.PastOrPresent.message=must be a date in the past or in the present
|
||||
org.apache.fineract.common.validation.Pattern.message=must match "{regexp}"
|
||||
org.apache.fineract.common.validation.Positive.message=must be greater than 0
|
||||
org.apache.fineract.common.validation.PositiveOrZero.message=must be greater than or equal to 0
|
||||
org.apache.fineract.common.validation.Size.message=size must be between {min} and {max}
|
||||
org.apache.fineract.common.validation.CreditCardNumber.message=invalid credit card number
|
||||
org.apache.fineract.common.validation.Currency.message=invalid currency (must be one of {value})
|
||||
org.apache.fineract.common.validation.EAN.message=invalid {type} barcode
|
||||
org.apache.fineract.common.validation.ISBN.message=invalid ISBN
|
||||
org.apache.fineract.common.validation.Length.message=length must be between {min} and {max}
|
||||
org.apache.fineract.common.validation.CodePointLength.message=length must be between {min} and {max}
|
||||
org.apache.fineract.common.validation.LuhnCheck.message=the check digit for ${validatedValue} is invalid, Luhn Modulo 10 checksum failed
|
||||
org.apache.fineract.common.validation.Mod10Check.message=the check digit for ${validatedValue} is invalid, Modulo 10 checksum failed
|
||||
org.apache.fineract.common.validation.Mod11Check.message=the check digit for ${validatedValue} is invalid, Modulo 11 checksum failed
|
||||
org.apache.fineract.common.validation.ModCheck.message=the check digit for ${validatedValue} is invalid, {modType} checksum failed
|
||||
org.apache.fineract.common.validation.Normalized.message=must be normalized
|
||||
org.apache.fineract.common.validation.ParametersScriptAssert.message=script expression "{script}" didn't evaluate to true
|
||||
org.apache.fineract.common.validation.Range.message=must be between {min} and {max}
|
||||
org.apache.fineract.common.validation.ScriptAssert.message=script expression "{script}" didn't evaluate to true
|
||||
org.apache.fineract.common.validation.UniqueElements.message=must only contain unique elements
|
||||
org.apache.fineract.common.validation.URL.message=must be a valid URL
|
||||
org.apache.fineract.common.validation.UUID.message=must be a valid UUID
|
||||
org.apache.fineract.common.validation.br.CNPJ.message=invalid Brazilian corporate taxpayer registry number (CNPJ)
|
||||
org.apache.fineract.common.validation.br.CPF.message=invalid Brazilian individual taxpayer registry number (CPF)
|
||||
org.apache.fineract.common.validation.br.TituloEleitoral.message=invalid Brazilian Voter ID card number
|
||||
org.apache.fineract.common.validation.pl.REGON.message=invalid Polish Taxpayer Identification Number (REGON)
|
||||
org.apache.fineract.common.validation.pl.NIP.message=invalid VAT Identification Number (NIP)
|
||||
org.apache.fineract.common.validation.pl.PESEL.message=invalid Polish National Identification Number (PESEL)
|
||||
org.apache.fineract.common.validation.ru.INN.message=invalid Russian taxpayer identification number (INN)
|
||||
org.apache.fineract.common.validation.time.DurationMax.message=must be shorter than${inclusive == true ? ' or equal to' : ''}${days == 0 ? '' : days == 1 ? ' 1 day' : ' ' += days += ' days'}${hours == 0 ? '' : hours == 1 ? ' 1 hour' : ' ' += hours += ' hours'}${minutes == 0 ? '' : minutes == 1 ? ' 1 minute' : ' ' += minutes += ' minutes'}${seconds == 0 ? '' : seconds == 1 ? ' 1 second' : ' ' += seconds += ' seconds'}${millis == 0 ? '' : millis == 1 ? ' 1 milli' : ' ' += millis += ' millis'}${nanos == 0 ? '' : nanos == 1 ? ' 1 nano' : ' ' += nanos += ' nanos'}
|
||||
org.apache.fineract.common.validation.time.DurationMin.message=must be longer than${inclusive == true ? ' or equal to' : ''}${days == 0 ? '' : days == 1 ? ' 1 day' : ' ' += days += ' days'}${hours == 0 ? '' : hours == 1 ? ' 1 hour' : ' ' += hours += ' hours'}${minutes == 0 ? '' : minutes == 1 ? ' 1 minute' : ' ' += minutes += ' minutes'}${seconds == 0 ? '' : seconds == 1 ? ' 1 second' : ' ' += seconds += ' seconds'}${millis == 0 ? '' : millis == 1 ? ' 1 milli' : ' ' += millis += ' millis'}${nanos == 0 ? '' : nanos == 1 ? ' 1 nano' : ' ' += nanos += ' nanos'}
|
||||
# dummy
|
||||
org.apache.fineract.dummy.request.content.not-empty=Dummy request content must have a value
|
||||
Reference in New Issue
Block a user