I have used Spring state-machine in quite a complex scenario. I will explain my problem with the simplest part of the SM. Refer below image. This is my main state machine
The state circled in red points to the following sub-machine
So, as you can see, I have 3 actions. sendBasicTemplate, timeoutLogAction and processBasicTemplateReply. I will provide the related code segments and my configuration below.
What I have observed during this process is that the state-machines created by the factory resides in memory always. There's some reference to it which i cannot think of.
Is it that the SM doesn't stop or is there anything I'm doing wrong? Here's my code.
Configuration class
#Configuration #EnableStateMachineFactory public class CambodiaStateMachine extends StateMachineConfigurerAdapter<String, String> {
#Override
public void configure(StateMachineModelConfigurer<String, String> model) throws Exception {
model
.withModel()
.factory(modelFactory());
}
#Override public void configure(StateMachineConfigurationConfigurer<String, String> config) throws Exception {
config
.withConfiguration()
.machineId("cambodia")
.autoStartup(true)
.listener(listener()); }
#Bean
public StateMachineListener<String, String> listener() {
return new StateMachineListenerAdapter<String, String>() {
#Override
public void stateChanged(State<String, String> from, State<String, String> to) {
System.out.println("State change to " + to.getId());
}
};
}
#Bean
public StateMachineModelFactory<String, String> modelFactory() {
return new UmlStateMachineModelFactory("classpath:stm/model.uml");
}
}
Methods : 1. This is how my events are fed to the machine and where new SM instances are made. I take my events from a queue
#RabbitListener(bindings = #QueueBinding(value = #Queue(value = "sims.events.mq", durable = "true"), exchange = #Exchange(type = ExchangeTypes.TOPIC, value = "sims.events.mq.xch", ignoreDeclarationExceptions = "true", durable = "true"), key = "events"))
public void process(GenericMessage<String> message) {
try {
String imei = (String) message.getHeaders().get("imei");
Subscriber subscriber = subscriberService.findSubscriber(imei);
// quickly create 'new' state machine
StateMachine<String, String> stateMachine = factory.getStateMachine();
stateMachine.addStateListener(new CompositeStateMachineListener<String, String>() {
#Override
public void stateContext(StateContext<String, String> arg0) {
String user = (String) arg0.getExtendedState().getVariables().get("imei");
if (user == null) {
return;
}
log.info(arg0.getStage().toString() + "**********" + stateMachine.getState());
try {
redisStateMachinePersister.persist(arg0.getStateMachine(), "testprefixSw:" + user);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
});
// restore from persistent
String user = (String) message.getHeaders().get("imei");
log.info(user);
// attempt restoring only if key is exist
if (redisTemplate.hasKey("testprefixSw:" + user)) {
System.out.println("************************ prefix exists...restoring");
resetStateMachineFromStore(stateMachine, user);
} else {
stateMachine.start();
System.out.println("************************ No prefix");
}
log.info("Payload == > " + message.getPayload());
try {
stateMachine.getExtendedState().getVariables().put("imei", user);
stateMachine.getExtendedState().getVariables().put("fromState", stateMachine.getState().getId());
stateMachine.getExtendedState().getVariables().put("eventName", message.getPayload());
if(null!= message.getHeaders().get("templates"))
stateMachine.getExtendedState().getVariables().put("templates", message.getHeaders().get("templates"));
if(null!= message.getHeaders().get("ttl"))
stateMachine.getExtendedState().getVariables().put("ttl", message.getHeaders().get("ttl"));
} catch (Exception e) {
log.error(e.getMessage(), e);
}
// check if state is properly restored...
log.info("Current State " + stateMachine.getState().toString());
feedMachine(stateMachine, user, message);
log.info("handler exited");
} catch (Exception e) {
log.error(e.getMessage(), e);
}
// TODO: save persistant state..
}
private void feedMachine(StateMachine<String, String> stateMachine, String user, GenericMessage<String> event)
throws Exception {
stateMachine.sendEvent(event);
System.out.println("persist machine --- > state :" + stateMachine.getState().toString());
redisStateMachinePersister.persist(stateMachine, "testprefixSw:" + user);
}
private StateMachine<String, String> resetStateMachineFromStore(StateMachine<String, String> stateMachine,
String user) throws Exception {
StateMachine<String, String> machine = redisStateMachinePersister.restore(stateMachine, "testprefixSw:" + user);
System.out.println("restore machine --- > state :" + machine.getState().toString());
return machine;
}
Actions
#Bean
public Action<String, String> sendBasicTemplate() {
// Action handler...
return new Action<String, String>() {
#Override
public void execute(StateContext<String, String> context) {
// MP: variables are the right way to do
String imeiNo = (String) context.getExtendedState().getVariables().get("imei");
String template = (String) context.getMessageHeader("template");
log.info("sending basic template " + template + " to " + imeiNo);
findTemplateNSend(context, template, imeiNo);
xbossBalanceCheck(context, imeiNo, "Direct Query");
setRiskyState(context, "testprefixSw:RISKY_StateBasic_WFT_Timeout" + imeiNo, 0);
}
};
}
#Bean
public Action<String, String> processBasicTemplateReply() {
// Action handler...
return new Action<String, String>() {
#Override
public void execute(StateContext<String, String> context) {
log.info("Result for basic template processing started");
log.info(context.getStateMachine().getState().getIds().toString());
String imeiNo = (String) context.getExtendedState().getVariables().get("imei");
saveDirectValues(context, imeiNo);
String fromState = (String) context.getExtendedState().getVariables().get("fromState");
String eventName = (String) context.getExtendedState().getVariables().get("eventName");
long trId = (Long) context.getMessageHeader("processId") != null? (Long) context.getMessageHeader("processId") : 0;
String key = "testprefixSw:RISKY_StateBasic_WFT_Timeout" + imeiNo;
log.info("*Going to delete if exists key ==>" + key);
if (clearRiskyStateIfSet(context, key)) {
log.info("------------------------------Jedis Exists");
sendSubscriberEventLog(imeiNo, fromState, context.getStateMachine().getState().getId(), trId, eventName, false, "Query Event Success");
}
// mark as success sent
context.getStateMachine().sendEvent("SEQUENCE_COMPLETE");
}
};
}
#Bean
public Action<String, String> timeoutLogAction() {
// Action handler...
return new Action<String, String>() {
#Override
public void execute(StateContext<String, String> context) {
// log.info("timeout log Action");
String imeiNo = (String) context.getStateMachine().getExtendedState().getVariables().get("imei");
// String imeiNo = (String)
// context.getExtendedState().getVariables().get("imei");
String fromState = (String) context.getExtendedState().getVariables().get("fromState");
String eventName = (String) context.getExtendedState().getVariables().get("eventName");
long trId = (Long) context.getMessageHeader("processId") != null ? (Long) context.getMessageHeader("processId") : 0;
String key = "testprefixSw:RISKY_StateBasic_WFT_Timeout" + imeiNo;
log.info("*Going to delete if exists key ==>" + key);
if (clearRiskyStateIfSet(context, key)) {
log.info("------------------------------Jedis Exists at timeout. Event Failed");
sendSubscriberEventLog(imeiNo, fromState, context.getStateMachine().getId(), trId, eventName, true, "Direct Query Failed due to Timeout");
sendAlert(imeiNo, EventPriority.NORMAL, "Direct Query Failed due to Timeout");
}
}
};
}
So based on the above, Is there anything I'm missing so that the created state machines are not collected by garbage? or any other explanation as to why memory is being consumed with each request and it never gets released?
Related
The idea is to allow ConcurrentSkipListMap to store only one ApprovalRequest which has unique customerId and its state is PENDING. I supplied overridden hashCode and equals implementations. Moreover, in unit test the ApprovalRequest suppose to create a new instance using lombok's #Builder. How to make it work?
#Component
public class LoanRepository {
private final ConcurrentSkipListMap<ApprovalRequest, ConcurrentHashMap<String, Decision>> pendingStorage;
public synchronized void saveAsPending(final LoanApprovalRequest loanApprovalRequest) {
log.info("Trying to save: {}", loanApprovalRequest);
if (pendingStorage.containsKey(loanApprovalRequest)) {
log.error("Attempt to save duplicate pending LoanApprovalRequest: {}", loanApprovalRequest);
throw new BusinessRuleException("Attempt to save duplicate pending LoanApprovalRequest: " + loanApprovalRequest);
}
ConcurrentHashMap<String, Decision> decisions = new ConcurrentHashMap<>();
for (Approver approver : loanApprovalRequest.getApprovers()) {
Decision pendingDecision = Decision.builder()
.customerId(loanApprovalRequest.getCustomerId())
.approverUsername(approver.getName())
.state(PENDING)
.build();
decisions.put(approver.getName(), pendingDecision);
}
if (pendingStorage.putIfAbsent(loanApprovalRequest, decisions) == null) {
log.info("Successfully added new LoanApprovalRequest: {}", loanApprovalRequest);
} else {
log.error("Save failed. Duplicate LoanApprovalRequest: {}", loanApprovalRequest);
throw new BusinessRuleException("Fail to add LoanApprovalRequest. Duplicate LoanApprovalRequest: " + loanApprovalRequest);
}
log.info("New storage size: {}", pendingStorage.size());
}
}
Test:
ConcurrentSkipListMap<ApprovalRequest, ConcurrentHashMap<String, Decision>> pendingStorage;
#BeforeEach
public void each() {
mainStorage = new ConcurrentSkipListMap<>();
pendingStorage = new ConcurrentSkipListMap<>();
repository = new LoanRepository(mainStorage, pendingStorage, threadPoolTaskScheduler);
}
#Order(2)
#Test
public void givenTwoProducers_whenSaving30LoanApprovalRequestsConcurrently_expectCorrectStatistics() throws InterruptedException {
final int numberOfThreads = 2;
final ExecutorService executorService = Executors.newFixedThreadPool(numberOfThreads);
CountDownLatch completedThreadCounter = new CountDownLatch(numberOfThreads);
CountDownLatch readyThreadCounter = new CountDownLatch(numberOfThreads);
CountDownLatch callingThreadBlocker = new CountDownLatch(1);
Runnable producer1 = () -> {
try {
readyThreadCounter.countDown();
callingThreadBlocker.await();
Set<Approver> approver = new HashSet<>();
approver.add(new Approver("Under €1_000 Approver"));
LoanApprovalRequest request;
for (int i = 0; i < 5; i++) {
request = LoanApprovalRequest.builder()
.customerId("1X-XXXX-XAX")
.decisionState(PENDING)
.loanAmount(BigDecimal.valueOf(123.01 + i))
.approvers(approver)
.timestamp(ZonedDateTime.now())
.build();
try {
repository.saveAsPending(request);
} catch (BusinessRuleException be) {
System.out.println(be.getMessage());
}
Thread.sleep(i * 10L);
}
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
} finally {
completedThreadCounter.countDown();
}
};
Runnable producer2 = () -> {
try {
readyThreadCounter.countDown();
callingThreadBlocker.await();
Set<Approver> approver = new HashSet<>();
approver.add(new Approver("Under €9_000 Approver"));
LoanApprovalRequest request;
for (int i = 0; i < 5; i++) {
request = LoanApprovalRequest.builder()
.customerId("2X-XXXX-XWX")
.loanAmount(BigDecimal.valueOf(1023.55 + i * 10))
.decisionState(PENDING)
.approvers(approver)
.timestamp(ZonedDateTime.now())
.build();
try {
repository.saveAsPending(request);
} catch (BusinessRuleException be) {
System.out.println(be.getMessage());
}
Thread.sleep(i * 10L);
}
completedThreadCounter.countDown();
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
} finally {
completedThreadCounter.countDown();
}
};
executorService.execute(producer1);
executorService.execute(producer2);
readyThreadCounter.await();
callingThreadBlocker.countDown();
completedThreadCounter.await();
executorService.shutdown();
Statistics statistics = repository.getStatistics(Duration.ofSeconds(60));
assertEquals(2, statistics.getCount());
}
LoanApprovalRequest
#Builder
#Data
#NoArgsConstructor
#AllArgsConstructor
public class LoanApprovalRequest implements ApprovalRequest, Comparable<LoanApprovalRequest> {
public LoanApprovalRequest(ZonedDateTime zonedDateTime) {
this.timestamp = zonedDateTime;
}
String customerId;
BigDecimal loanAmount;
Set<Approver> approvers;
ZonedDateTime timestamp;
DecisionState decisionState;
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
LoanApprovalRequest that = (LoanApprovalRequest) o;
return customerId.equals(that.customerId);
}
#Override
public int hashCode() {
return customerId.hashCode();
}
#Override
public int compareTo(LoanApprovalRequest o) {
return this.timestamp.compareTo(o.timestamp);
}
#Override
public String toString() {
return "LoanApprovalRequest{" +
"customerId='" + customerId + '\'' +
", loanAmount=" + loanAmount +
// ", approvers=[" + approvers.stream().map(Approver::getName).collect(Collectors.joining(",")) + "]" +
// ", timestamp=" + timestamp.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss:nnnnnnnnn").withZone(ZoneId.of("UTC"))) +
", decisionState=" + decisionState +
'}';
}
}
ConcurrentSkipListMap is not based on hash codes, but on ordering/comparisons.
So you will have to use that customerId in compareTo as well (or supply the Map with a different Comparator based on customerId). Otherwise it will not be consistent with equals and the Map key uniqueness checks won't work.
same record is being written to topic. not pulling latest records from splunk. time parameters are set in start method to pull last one min data. Any inputs.
currently i dont set offset from source. when poll is run every time, does it look for source offset and then poll? in logs can we have time as offset.
#Override
public List<SourceRecord> poll() throws InterruptedException {
List<SourceRecord> results = new ArrayList<>();
Map<String, String> recordProperties = new HashMap<String, String>();
while (true) {
try {
String line = null;
InputStream stream = job.getResults(previewArgs);
String earlierKey = null;
String value = null;
ResultsReaderCsv csv = new ResultsReaderCsv(stream);
HashMap<String, String> event;
while ((event = csv.getNextEvent()) != null) {
for (String key: event.keySet()) {
if(key.equals("rawlogs")){
recordProperties.put("rawlogs", event.get(key)); results.add(extractRecord(Splunklog.SplunkLogSchema(), line, recordProperties));
return results;}}}
csv.close();
stream.close();
Thread.sleep(500);
} catch(Exception ex) {
System.out.println("Exception occurred : " + ex);
}
}
}
private SourceRecord extractRecord(Schema schema, String line, Map<String, String> recordProperties) {
Map<String, String> sourcePartition = Collections.singletonMap(FILENAME_FIELD, FILENAME);
Map<String, String> sourceOffset = Collections.singletonMap(POSITION_FIELD, recordProperties.get(OFFSET_KEY));
return new SourceRecord(sourcePartition, sourceOffset, TOPIC_NAME, schema, recordProperties);
}
#Override
public void start(Map<String, String> properties) {
try {
config = new SplunkSourceTaskConfig(properties);
} catch (ConfigException e) {
throw new ConnectException("Couldn't start SplunkSourceTask due to configuration error", e);
}
HttpService.setSslSecurityProtocol(SSLSecurityProtocol.TLSv1_2);
Service service = new Service("splnkip", port);
String credentials = "user:pwd";
String basicAuthHeader = Base64.encode(credentials.getBytes());
service.setToken("Basic " + basicAuthHeader);
String startOffset = readOffset();
JobArgs jobArgs = new JobArgs();
if (startOffset != null) {
log.info("-------------------------------task OFFSET!NULL ");
jobArgs.setExecutionMode(JobArgs.ExecutionMode.BLOCKING);
jobArgs.setSearchMode(JobArgs.SearchMode.NORMAL);
jobArgs.setEarliestTime(startOffset);
jobArgs.setLatestTime("now");
jobArgs.setStatusBuckets(300);
} else {
log.info("-------------------------------task OFFSET=NULL ");
jobArgs.setExecutionMode(JobArgs.ExecutionMode.BLOCKING);
jobArgs.setSearchMode(JobArgs.SearchMode.NORMAL);
jobArgs.setEarliestTime("+419m");
jobArgs.setLatestTime("+420m");
jobArgs.setStatusBuckets(300);
}
String mySearch = "search host=search query";
job = service.search(mySearch, jobArgs);
while (!job.isReady()) {
try {
Thread.sleep(500);
} catch (InterruptedException ex) {
log.error("Exception occurred while waiting for job to start: " + ex);
}
}
previewArgs = new JobResultsPreviewArgs();
previewArgs.put("output_mode", "csv");
stop = new AtomicBoolean(false);
}
I have a spring project, and I need to use cache in my service class, like this:
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
#Service
public class WebAuthnServer {
private final Cache<String, RegistrationRequest> registerRequestStorage = newCache();
private static <K, V> Cache<K, V> newCache() {
return CacheBuilder.newBuilder()
.maximumSize(100)
.expireAfterAccess(10, TimeUnit.MINUTES)
.build();
}
public Either<String, RegistrationRequest> startRegistration(String username, String displayName, String credentialNickname, boolean requireResidentKey) {
if (userStorage.getRegistrationsByUsername(username).isEmpty()) {
RegistrationRequest request = new RegistrationRequest(...);
registerRequestStorage.put(request.getRequestId(), request);
} else {
return new Left("The username \"" + username + "\" is already registered.");
}
}
}
I have registerRequestStorage cache, and I put some data in cache using the method startRegistration. But when I try to get this data in another method, the cache is empty.
public Either<List<String>, SuccessfulRegistrationResult> finishRegistration(String responseJson) {
RegistrationResponse response = null;
try {
response = jsonMapper.readValue(responseJson, RegistrationResponse.class);
} catch (IOException e) {
return Left.apply(Arrays.asList("Registration failed!", "Failed to decode response object.", e.getMessage()));
}
RegistrationRequest request = registerRequestStorage.getIfPresent(response.getRequestId());
registerRequestStorage.invalidate(response.getRequestId());
if (request == null) {
logger.info("fail finishRegistration responseJson: {}", responseJson);
return Left.apply(Arrays.asList("Registration failed!", "No such registration in progress."));
} else {
Try<RegistrationResult> registrationTry = rp.finishRegistration(
request.getPublicKeyCredentialCreationOptions(),
response.getCredential(),
Optional.empty()
);
}
}
I have a problem with getting a bean from application context when using Spring IoC.
I have a ContactProvider interface for my phone book application and 3 implementations of this interface. All implementations are Spring #Components. I also have a Preferences which stores which of the implementation is used by the user. When the user tries to change the implementation the application throws NoSuchBeanDefinitionException. To find out which implementation the user wants to use based on his input I created utility enum with all of my implementations that provides Class objects for all of the implementation. This class has fromString() method which returns implementation type based on given string (which is user input).
Method for setting a new contact provider:
private void handleSetProviderCommand(String param) {
ContactProviders providerType = ContactProviders.fromString(param);
Class<? extends ContactProvider> providerClass = providerType.getProviderClass();
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
ContactProvider contactProvider = context.getBean(providerClass); // I get NoSuchBeanDefinitionException here
phoneBook.setContactProvider(contactProvider);
Preferences preferences = Preferences.userNodeForPackage(PhoneBook.class);
preferences.put(PreferenceKeys.CONTACT_PROVIDER.getKey(), param);
}
ContactProviders enum, from which I take a class of the implementation:
public enum ContactProviders {
FILE(FileContactProvider.SHORT_NAME, FileContactProvider.class),
ELASTIC(ElasticContactProvider.SHORT_NAME, ElasticContactProvider.class),
NO_SQL(NoSqlContactProvider.SHORT_NAME, NoSqlContactProvider.class);
private final Class<? extends ContactProvider> providerClass;
private final String shortName;
ContactProviders(String shortName, Class<? extends ContactProvider> providerClass) {
this.shortName = shortName;
this.providerClass = providerClass;
}
public static ContactProviders fromString(String param) {
Optional<ContactProviders> providerType = Arrays.stream(ContactProviders.values())
.filter(cp -> cp.getShortName().equals(param)).findFirst();
if (providerType.isPresent()) {
return providerType.get();
} else {
throw new IllegalArgumentException("Contact provider not recognized: " + param);
}
}
}
One of the implementations (all of them are annotated with #Component):
public class FileContactProvider implements ContactProvider {
public static final String SHORT_NAME = "file";
private static final String SEPARATOR = "|";
private List<Contact> contacts;
private Path file;
private Logger logger = LoggerFactory.getLogger(FileContactProvider.class);
public FileContactProvider() {
file = openFile();
contacts = loadContacts();
}
private Path openFile() {
Preferences preferences = Preferences.userNodeForPackage(FileContactProvider.class);
Path file = Paths.get(
preferences.get(
PreferenceKeys.FILE_FILE_NAME.getKey(),
PreferenceKeys.FILE_FILE_NAME.getDefaultValue()
)
);
if (Files.exists(file)) {
try {
if (Files.isReadable(file) && Files.isWritable(file)) {
return file;
} else {
throw new IOException("The file is not readable or writable.");
}
} catch (IOException e) {
logger.error("Error while opening the file: " + e.getMessage());
}
} else {
try {
Files.createFile(file);
} catch (IOException e) {
logger.error("Error while creating a file: " + e.getMessage());
}
}
return file;
}
private void saveFile() {
List<String> contactStrings = new ArrayList<>();
contacts.stream()
.sorted()
.forEach(contact -> contactStrings.add(contact.getName() + SEPARATOR + contact.getNumber()));
try {
Files.write(file, contactStrings);
} catch (IOException e) {
logger.error("Error while saving to the file: " + e.getMessage());
}
}
private List<Contact> loadContacts() {
List<Contact> loaded = new ArrayList<>();
try {
Files.readAllLines(file)
.forEach(line -> {
String[] contactString = StringUtils.split(line, SEPARATOR);
Contact contact = new Contact(contactString[0], contactString[1]);
loaded.add(contact);
});
} catch (IOException e) {
logger.error("Error while reading file content: " + e.getMessage());
}
return loaded;
}
#Override
public Contact save(Contact contact) {
if (contacts.contains(contact)) {
System.out.println("Contact is already in the phone book.");
} else {
contacts.add(contact);
saveFile();
}
return contact;
}
#Override
public List<Contact> find(String name) throws ContactNotFoundException {
List<Contact> found = contacts.stream()
.filter(contact -> StringUtils.indexOf(contact.getName().toLowerCase(), name.toLowerCase()) != -1)
.collect(Collectors.toList());
if (found.isEmpty()) {
throw new ContactNotFoundException(name);
}
return found;
}
#Override
public List<Contact> getAll() {
return contacts;
}
#Override
public void delete(Contact contact) {
contacts.remove(contact);
saveFile();
}
#Override
public void configure() {
System.out.print("Enter file name: ");
String filename = ConsoleUtils.getInput("^[^<>:;,?\"*|/]+$");
Preferences preferences = Preferences.userNodeForPackage(FileContactProvider.class);
preferences.put(PreferenceKeys.FILE_FILE_NAME.getKey(), filename);
file = openFile();
contacts = loadContacts();
}
#Override
public void showConfiguration() {
Preferences preferences = Preferences.userNodeForPackage(FileContactProvider.class);
System.out.println("File name: " +
preferences.get(
PreferenceKeys.FILE_FILE_NAME.getKey(),
PreferenceKeys.FILE_FILE_NAME.getDefaultValue()
)
);
}
}
I also have a bean defined in my #Configuration class to provide a contact provider based on preference already set in the system. It takes preference string and creates an instance of implementation class to return:
#Bean
public ContactProvider contactProvider() {
Preferences preferences = Preferences.userNodeForPackage(PhoneBook.class);
String preferredProviderName = preferences.get(PreferenceKeys.CONTACT_PROVIDER.toString(),
ContactProviders.FILE.getShortName());
try {
Class<? extends ContactProvider> providerClass = ContactProviders.fromString(preferredProviderName)
.getProviderClass();
Constructor<? extends ContactProvider> constructor = providerClass.getConstructor();
return constructor.newInstance();
} catch (IllegalArgumentException e) {
logger.error("Can't get provider from given string: " + e.getMessage());
} catch (NoSuchMethodException e) {
logger.error("Can't find provider's constructor: " + e.getMessage());
} catch (IllegalAccessException e) {
logger.error("Can't get access to provider's constructor: " + e.getMessage());
} catch (InstantiationException e) {
logger.error("Can instantiate provider: " + e.getMessage());
} catch (InvocationTargetException e) {
logger.error("Can't invoke provider's constructor: " + e.getMessage());
}
logger.info("Couldn't get provider from preferences. Default file contact provider instantiated.");
return new FileContactProvider();
}
I have problem of playing back the recorded media file from red5 published stream, following is my code. I could see a file called out.flv is created, but this out.flv can not be played back.
public class Red5ClientTest {
private static Timer timer;
private static RTMPClient client;
private static String sourceStreamName;
private static int videoTs;
private static int audioTs;
private static FLVWriter writer;
private static int bytesRead =0;
public static void main(String[] args) throws IOException {
String sourceHost = "localhost";
int sourcePort = 1935;
String sourceApp = "oflaDemo";
sourceStreamName = "myStream";
timer = new Timer();
client = new RTMPClient();
String path = "c:\\temp\\out.flv";
File file = new File(path);
if (!file.exists()) {
file.createNewFile();
}
writer = new FLVWriter(file,true);
client.setStreamEventDispatcher(new StreamEventDispatcher());
client.setStreamEventHandler(new INetStreamEventHandler() {
public void onStreamEvent(Notify notify) {
System.out.printf("onStreamEvent: %s\n", notify);
ObjectMap<?, ?> map = (ObjectMap<?, ?>) notify.getCall().getArguments()[0];
String code = (String) map.get("code");
System.out.printf("<:%s\n", code);
if (StatusCodes.NS_PLAY_STREAMNOTFOUND.equals(code)) {
System.out.println("Requested stream was not found");
client.disconnect();
}
else if (StatusCodes.NS_PLAY_UNPUBLISHNOTIFY.equals(code)
|| StatusCodes.NS_PLAY_COMPLETE.equals(code)) {
System.out.println("Source has stopped publishing or play is complete");
client.disconnect();
}
}
});
client.setConnectionClosedHandler(new Runnable() {
public void run() {
if (writer != null) {
writer.close();
}
System.out.println("Source connection has been closed, proxy will be stopped");
System.exit(0);
}
});
client.setExceptionHandler(new ClientExceptionHandler() {
#Override
public void handleException(Throwable throwable) {
throwable.printStackTrace();
System.exit(1);
}
});
// connect the consumer
Map<String, Object> defParams = client.makeDefaultConnectionParams(sourceHost, sourcePort,
sourceApp);
// add pageurl and swfurl
defParams.put("pageUrl", "");
defParams.put("swfUrl", "app:/Red5-StreamRelay.swf");
// indicate for the handshake to generate swf verification data
client.setSwfVerification(true);
// connect the client
client.connect(sourceHost, sourcePort, defParams, new IPendingServiceCallback() {
public void resultReceived(IPendingServiceCall call) {
System.out.println("connectCallback");
ObjectMap<?, ?> map = (ObjectMap<?, ?>) call.getResult();
String code = (String) map.get("code");
if ("NetConnection.Connect.Rejected".equals(code)) {
System.out.printf("Rejected: %s\n", map.get("description"));
client.disconnect();
//proxy.stop();
}
else if ("NetConnection.Connect.Success".equals(code)) {
// 1. Wait for onBWDone
timer.schedule(new BandwidthStatusTask(), 2000L);
Object result = call.getResult();
System.out.println("Red5ClientTest.main()");
}
else {
System.out.printf("Unhandled response code: %s\n", code);
}
}
});
// keep sleeping main thread while the proxy runs
// kill the timer
//timer.cancel();
System.out.println("Stream relay exit");
}
/**
* Handles result from subscribe call.
*/
private static final class SubscribeStreamCallBack implements IPendingServiceCallback {
public void resultReceived(IPendingServiceCall call) {
System.out.println("resultReceived: " + call);
Object result = call.getResult();
System.out.println("results came {}" + result);
}
}
private static final class StreamEventDispatcher implements IEventDispatcher {
public void dispatchEvent(IEvent event) {
System.out.println("ClientStream.dispachEvent()" + event.toString());
try {
//RTMPMessage build = RTMPMessage.build((IRTMPEvent) event);
IRTMPEvent rtmpEvent = (IRTMPEvent) event;
ITag tag = new Tag();
tag.setDataType(rtmpEvent.getDataType());
if (rtmpEvent instanceof VideoData) {
videoTs += rtmpEvent.getTimestamp();
tag.setTimestamp(videoTs);
}
else if (rtmpEvent instanceof AudioData) {
audioTs += rtmpEvent.getTimestamp();
tag.setTimestamp(audioTs);
}
IoBuffer data = ((IStreamData) rtmpEvent).getData().asReadOnlyBuffer();
tag.setBodySize(data.limit());
tag.setBody(data);
try {
writer.writeTag(tag);
} catch (Exception e) {
throw new RuntimeException(e);
}
System.out.println("writting....");
}
catch (Exception e) {//IOException
e.printStackTrace();
}
}
}
private static final class BandwidthStatusTask extends TimerTask {
#Override
public void run() {
// check for onBWDone
System.out.println("Bandwidth check done: " + client.isBandwidthCheckDone());
// cancel this task
this.cancel();
// create a task to wait for subscribed
timer.schedule(new PlayStatusTask(), 1000L);
// 2. send FCSubscribe
client.subscribe(new SubscribeStreamCallBack(), new Object[] { sourceStreamName });
}
}
private static final class PlayStatusTask extends TimerTask {
#Override
public void run() {
// checking subscribed
System.out.println("Subscribed: " + client.isSubscribed());
// cancel this task
this.cancel();
// 3. create stream
client.createStream(new CreateStreamCallback());
}
}
/**
* Creates a "stream" via playback, this is the source stream.
*/
private static final class CreateStreamCallback implements IPendingServiceCallback {
public void resultReceived(IPendingServiceCall call) {
System.out.println("resultReceived: " + call);
int streamId = ((Number) call.getResult()).intValue();
System.out.println("stream id: " + streamId);
// send our buffer size request
if (sourceStreamName.endsWith(".flv") || sourceStreamName.endsWith(".f4v")
|| sourceStreamName.endsWith(".mp4")) {
client.play(streamId, sourceStreamName, 0, -1);
}
else {
client.play(streamId, sourceStreamName, -1, 0);
}
}
}
}
what could I be doing possibly wrong here?
Finally got it
public class TeqniRTMPClient {
private static final Logger logger = LoggerFactory.getLogger(MyRtmpClient.class);
public static void main(String args[]) throws IOException {
TeqniRTMPClient client = new TeqniRTMPClient("localhost", 1935, "oflaDemo", "myStream");
client.recordStream();
}
private RTMPClient client;
private ITagWriter writer;
private String sourceHost;
private int sourcePort;
private String sourceApp;
private String sourceStreamName;
private int lastTimestamp;
private int startTimestamp = -1;
public TeqniRTMPClient(String sourceHost, int sourcePort, String sourceApp,
String sourceStreamName) {
super();
this.sourceHost = sourceHost;
this.sourcePort = sourcePort;
this.sourceApp = sourceApp;
this.sourceStreamName = sourceStreamName;
}
public void recordStream() throws IOException {
client = new RTMPClient();
String path = "c:\\temp\\out.flv";
File file = new File(path);
if (!file.exists()) {
file.createNewFile();
}
FLVService flvService = new FLVService();
flvService.setGenerateMetadata(true);
try {
IStreamableFile flv = flvService.getStreamableFile(file);
writer = flv.getWriter();
}
catch (Exception e) {
throw new RuntimeException(e);
}
client.setStreamEventDispatcher(new StreamEventDispatcher());
client.setStreamEventHandler(new INetStreamEventHandler() {
public void onStreamEvent(Notify notify) {
System.out.printf("onStreamEvent: %s\n", notify);
ObjectMap<?, ?> map = (ObjectMap<?, ?>) notify.getCall().getArguments()[0];
String code = (String) map.get("code");
System.out.printf("<:%s\n", code);
if (StatusCodes.NS_PLAY_STREAMNOTFOUND.equals(code)) {
System.out.println("Requested stream was not found");
client.disconnect();
}
else if (StatusCodes.NS_PLAY_UNPUBLISHNOTIFY.equals(code)
|| StatusCodes.NS_PLAY_COMPLETE.equals(code)) {
System.out.println("Source has stopped publishing or play is complete");
client.disconnect();
}
}
});
client.setExceptionHandler(new ClientExceptionHandler() {
#Override
public void handleException(Throwable throwable) {
throwable.printStackTrace();
System.exit(1);
}
});
client.setConnectionClosedHandler(new Runnable() {
public void run() {
if (writer != null) {
writer.close();
}
System.out.println("Source connection has been closed, proxy will be stopped");
System.exit(0);
}
});
// connect the consumer
Map<String, Object> defParams = client.makeDefaultConnectionParams(sourceHost, sourcePort,
sourceApp);
// add pageurl and swfurl
defParams.put("pageUrl", "");
defParams.put("swfUrl", "app:/Red5-StreamRelay.swf");
// indicate for the handshake to generate swf verification data
client.setSwfVerification(true);
// connect the client
client.connect(sourceHost, sourcePort, defParams, new IPendingServiceCallback() {
public void resultReceived(IPendingServiceCall call) {
System.out.println("connectCallback");
ObjectMap<?, ?> map = (ObjectMap<?, ?>) call.getResult();
String code = (String) map.get("code");
if ("NetConnection.Connect.Rejected".equals(code)) {
System.out.printf("Rejected: %s\n", map.get("description"));
client.disconnect();
}
else if ("NetConnection.Connect.Success".equals(code)) {
// 1. Wait for onBWDone
client.createStream(new CreateStreamCallback());
Object result = call.getResult();
System.out.println("Red5ClientTest.main()");
}
else {
System.out.printf("Unhandled response code: %s\n", code);
}
}
});
}
class CreateStreamCallback implements IPendingServiceCallback {
public void resultReceived(IPendingServiceCall call) {
System.out.println("resultReceived: " + call);
int streamId = ((Number) call.getResult()).intValue();
System.out.println("stream id: " + streamId);
// send our buffer size request
if (sourceStreamName.endsWith(".flv") || sourceStreamName.endsWith(".f4v")
|| sourceStreamName.endsWith(".mp4")) {
client.play(streamId, sourceStreamName, 0, -1);
}
else {
client.play(streamId, sourceStreamName, -1, 0);
}
}
}
class StreamEventDispatcher implements IEventDispatcher {
private int videoTs;
private int audioTs;
public void dispatchEvent(IEvent event) {
System.out.println("ClientStream.dispachEvent()" + event.toString());
try {
IRTMPEvent rtmpEvent = (IRTMPEvent) event;
logger.debug("rtmp event: " + rtmpEvent.getHeader() + ", "
+ rtmpEvent.getClass().getSimpleName());
if (!(rtmpEvent instanceof IStreamData)) {
logger.debug("skipping non stream data");
return;
}
if (rtmpEvent.getHeader().getSize() == 0) {
logger.debug("skipping event where size == 0");
return;
}
byte dataType = rtmpEvent.getDataType();
ITag tag = new Tag();
tag.setDataType(dataType);
if (rtmpEvent instanceof VideoData) {
VideoData video = (VideoData) rtmpEvent;
FrameType frameType = video.getFrameType();
videoTs += rtmpEvent.getTimestamp();
tag.setTimestamp(videoTs);
}
else if (rtmpEvent instanceof AudioData) {
audioTs += rtmpEvent.getTimestamp();
tag.setTimestamp(audioTs);
}
IoBuffer data = ((IStreamData) rtmpEvent).getData().asReadOnlyBuffer();
tag.setBodySize(data.limit());
tag.setBody(data);
try {
writer.writeTag(tag);
}
catch (Exception e) {
throw new RuntimeException(e);
}
System.out.println("writting....");
}
catch (Exception e) {//IOException
e.printStackTrace();
}
}
}
}