diff options
20 files changed, 1758 insertions, 0 deletions
@@ -96,6 +96,9 @@ ifeq ("$(shell lsb_release -si)", "Ubuntu") echo "by executing \"make install-dep\"\n" ; \ exit 1 ; \ fi ; \ + if [ "$(shell lsb_release -r | awk '{print $$2}')"=="14.04" ]; then \ + sudo update-alternatives --set javah /usr/lib/jvm/java-8-openjdk-amd64/bin/javah ; \ + fi ; \ exit 0 endif @echo "SOURCE_PATH = $(WS_ROOT)" > $(BR)/build-config.mk @@ -121,6 +124,9 @@ install-dep: ifeq ("$(shell lsb_release -si)", "Ubuntu") $(use_ppa_for_jdk8) @sudo apt-get -y install $(DEB_DEPENDS) + if [ "$(shell lsb_release -r | awk '{print $$2}')"=="14.04" ]; then \ + update-alternatives --set javah /usr/lib/jvm/java-8-openjdk-amd64/bin/javah ; \ + fi ; \ else ifneq ("$(wildcard /etc/redhat-release)","") @sudo yum groupinstall -y $(RPM_DEPENDS_GROUPS) @sudo yum install -y $(RPM_DEPENDS) diff --git a/vpp-api/java/jvpp/gen/callback_gen.py b/vpp-api/java/jvpp/gen/callback_gen.py new file mode 100644 index 00000000..218ac622 --- /dev/null +++ b/vpp-api/java/jvpp/gen/callback_gen.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python +# +# Copyright (c) 2016 Cisco and/or its affiliates. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import util +from string import Template + +from util import remove_suffix + +callback_suffix = "Callback" + +callback_template = Template(""" +package $base_package.$callback_package; + +/** + * $docs + */ +public interface $cls_name extends $base_package.$callback_package.JVppCallback { + + $callback_method + +} +""") + +global_callback_template = Template(""" +package $base_package.$callback_package; + +/** + * + * + * Global aggregated callback interface + */ +public interface JVppGlobalCallback extends $callbacks { +} +""") + + +def generate_callbacks(func_list, base_package, callback_package, dto_package): + """ Generates callback interfaces """ + print "Generating Callback interfaces" + + if not os.path.exists(callback_package): + raise Exception("%s folder is missing" % callback_package) + + callbacks = [] + for func in func_list: + + if util.is_notification(func['name']) or util.is_ignored(func['name']): + # FIXME handle notifications + continue + + camel_case_name_with_suffix = util.underscore_to_camelcase_upper(func['name']) + if not util.is_reply(camel_case_name_with_suffix): + continue + + camel_case_name = util.remove_reply_suffix(camel_case_name_with_suffix) + callbacks.append("{0}.{1}.{2}".format(base_package, callback_package, camel_case_name + callback_suffix)) + callback_path = os.path.join(callback_package, camel_case_name + callback_suffix + ".java") + callback_file = open(callback_path, 'w') + + reply_type = "%s.%s.%s" % (base_package, dto_package, camel_case_name_with_suffix) + method = "void on{0}({1} reply);".format(camel_case_name_with_suffix, reply_type) + callback_file.write( + callback_template.substitute(docs='Generated from ' + str(func), + cls_name=camel_case_name + callback_suffix, + callback_method=method, + base_package=base_package, + callback_package=callback_package)) + callback_file.flush() + callback_file.close() + + callback_file = open(os.path.join(callback_package, "JVppGlobalCallback.java"), 'w') + callback_file.write(global_callback_template.substitute(callbacks=", ".join(callbacks), + base_package=base_package, + callback_package=callback_package)) + callback_file.flush() + callback_file.close() diff --git a/vpp-api/java/jvpp/gen/dto_gen.py b/vpp-api/java/jvpp/gen/dto_gen.py new file mode 100644 index 00000000..17fde68a --- /dev/null +++ b/vpp-api/java/jvpp/gen/dto_gen.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python +# +# Copyright (c) 2016 Cisco and/or its affiliates. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os, util +from string import Template + +dto_template = Template(""" +package $base_package.$dto_package; + +/** + * $docs + */ +public final class $cls_name implements $base_package.$dto_package.$base_type { + +$fields +$methods +} +""") + +field_template = Template(""" public $type $name;\n""") + +send_template = Template(""" @Override + public int send(final $base_package.JVpp jvpp) { + return jvpp.$method_name($args); + }\n""") + + +def generate_dtos(func_list, base_package, dto_package): + """ Generates dto objects in a dedicated package """ + print "Generating DTOs" + + if not os.path.exists(dto_package): + raise Exception("%s folder is missing" % dto_package) + + for func in func_list: + camel_case_dto_name = util.underscore_to_camelcase_upper(func['name']) + camel_case_method_name = util.underscore_to_camelcase(func['name']) + dto_path = os.path.join(dto_package, camel_case_dto_name + ".java") + + if util.is_notification(func['name']) or util.is_ignored(func['name']): + # TODO handle notifications + continue + + fields = "" + for t in zip(func['types'], func['args']): + fields += field_template.substitute(type=util.jni_2_java_type_mapping[t[0]], + name=util.underscore_to_camelcase(t[1])) + methods = "" + base_type = "" + if util.is_reply(camel_case_dto_name): + request_dto_name = get_request_name(camel_case_dto_name, func['name']) + if util.is_details(camel_case_dto_name): + # FIXME assumption that dump calls end with "Dump" suffix. Not enforced in vpe.api + base_type += "JVppReply<%s.%s.%s>" % (base_package, dto_package, request_dto_name + "Dump") + generate_dump_reply_dto(request_dto_name, base_package, dto_package, camel_case_dto_name, + camel_case_method_name, func) + else: + base_type += "JVppReply<%s.%s.%s>" % (base_package, dto_package, request_dto_name) + else: + args = "" if fields is "" else "this" + methods = send_template.substitute(method_name=camel_case_method_name, + base_package=base_package, + args=args) + if util.is_dump(camel_case_dto_name): + base_type += "JVppDump" + else: + base_type += "JVppRequest" + + dto_file = open(dto_path, 'w') + dto_file.write(dto_template.substitute(docs='Generated from ' + str(func), + cls_name=camel_case_dto_name, + fields=fields, + methods=methods, + base_package=base_package, + base_type=base_type, + dto_package=dto_package)) + dto_file.flush() + dto_file.close() + + flush_dump_reply_dtos() + + +dump_dto_suffix = "ReplyDump" +dump_reply_artificial_dtos = {} + + +# Returns request name or special one from unconventional_naming_rep_req map +def get_request_name(camel_case_dto_name, func_name): + return util.underscore_to_camelcase_upper( + util.unconventional_naming_rep_req[func_name]) if func_name in util.unconventional_naming_rep_req \ + else util.remove_reply_suffix(camel_case_dto_name) + + +def flush_dump_reply_dtos(): + for dump_reply_artificial_dto in dump_reply_artificial_dtos.values(): + dto_path = os.path.join(dump_reply_artificial_dto['dto_package'], + dump_reply_artificial_dto['cls_name'] + ".java") + dto_file = open(dto_path, 'w') + dto_file.write(dto_template.substitute(docs=dump_reply_artificial_dto['docs'], + cls_name=dump_reply_artificial_dto['cls_name'], + fields=dump_reply_artificial_dto['fields'], + methods=dump_reply_artificial_dto['methods'], + base_package=dump_reply_artificial_dto['base_package'], + base_type=dump_reply_artificial_dto['base_type'], + dto_package=dump_reply_artificial_dto['dto_package'])) + dto_file.flush() + dto_file.close() + + +def generate_dump_reply_dto(request_dto_name, base_package, dto_package, camel_case_dto_name, camel_case_method_name, + func): + base_type = "JVppReplyDump<%s.%s.%s, %s.%s.%s>" % ( + base_package, dto_package, util.remove_reply_suffix(camel_case_dto_name) + "Dump", + base_package, dto_package, camel_case_dto_name) + fields = " public java.util.List<%s> %s = new java.util.ArrayList<>();" % (camel_case_dto_name, camel_case_method_name) + cls_name = camel_case_dto_name + dump_dto_suffix + + # In case of already existing artificial reply dump DTO, just update it + # Used for sub-dump dtos + if request_dto_name in dump_reply_artificial_dtos.keys(): + dump_reply_artificial_dtos[request_dto_name]['fields'] = \ + dump_reply_artificial_dtos[request_dto_name]['fields'] + '\n' + fields + else: + dump_reply_artificial_dtos[request_dto_name] = ({'docs': 'Dump reply wrapper generated from ' + str(func), + 'cls_name': cls_name, + 'fields': fields, + 'methods': "", + 'base_package': base_package, + 'base_type': base_type, + 'dto_package': dto_package, + }) diff --git a/vpp-api/java/jvpp/gen/jvpp_callback_facade_gen.py b/vpp-api/java/jvpp/gen/jvpp_callback_facade_gen.py new file mode 100644 index 00000000..731bd894 --- /dev/null +++ b/vpp-api/java/jvpp/gen/jvpp_callback_facade_gen.py @@ -0,0 +1,236 @@ +#!/usr/bin/env python +# +# Copyright (c) 2016 Cisco and/or its affiliates. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os, util +from string import Template + +import callback_gen +import dto_gen + +jvpp_ifc_template = Template(""" +package $base_package.$callback_facade_package; + +public interface CallbackJVpp extends java.lang.AutoCloseable { + + @Override + void close(); + + // TODO add send + +$methods +} +""") + +jvpp_impl_template = Template(""" +package $base_package.$callback_facade_package; + +public final class CallbackJVppFacade implements $base_package.$callback_facade_package.CallbackJVpp { + + private final $base_package.JVpp jvpp; + private final java.util.Map<Integer, $base_package.$callback_package.JVppCallback> callbacks; + + public CallbackJVppFacade(final $base_package.JVpp jvpp, + java.util.Map<Integer, $base_package.$callback_package.JVppCallback> callbacks) { + if(jvpp == null) { + throw new java.lang.NullPointerException("jvpp is null"); + } + this.jvpp = jvpp; + this.callbacks = callbacks; + } + + @Override + public void close() { + } + + // TODO add send() + +$methods +} +""") + +method_template = Template( + """ void $name($base_package.$dto_package.$request request, $base_package.$callback_package.$callback callback);""") +method_impl_template = Template(""" public final void $name($base_package.$dto_package.$request request, $base_package.$callback_package.$callback callback) { + synchronized (callbacks) { + callbacks.put(jvpp.$name(request), callback); + } + } +""") + +no_arg_method_template = Template(""" void $name($base_package.$callback_package.$callback callback);""") +no_arg_method_impl_template = Template(""" public final void $name($base_package.$callback_package.$callback callback) { + synchronized (callbacks) { + callbacks.put(jvpp.$name(), callback); + } + } +""") + + +def generate_jvpp(func_list, base_package, dto_package, callback_package, callback_facade_package): + """ Generates callback facade """ + print "Generating JVpp callback facade" + + if os.path.exists(callback_facade_package): + util.remove_folder(callback_facade_package) + + os.mkdir(callback_facade_package) + + methods = [] + methods_impl = [] + for func in func_list: + + if util.is_notification(func['name']) or util.is_ignored(func['name']): + # TODO handle notifications + continue + + camel_case_name = util.underscore_to_camelcase(func['name']) + camel_case_name_upper = util.underscore_to_camelcase_upper(func['name']) + if util.is_reply(camel_case_name): + continue + + # Strip suffix for dump calls + callback_type = get_request_name(camel_case_name_upper, func['name']) + callback_gen.callback_suffix + + if len(func['args']) == 0: + methods.append(no_arg_method_template.substitute(name=camel_case_name, + base_package=base_package, + dto_package=dto_package, + callback_package=callback_package, + callback=callback_type)) + methods_impl.append(no_arg_method_impl_template.substitute(name=camel_case_name, + base_package=base_package, + dto_package=dto_package, + callback_package=callback_package, + callback=callback_type)) + else: + methods.append(method_template.substitute(name=camel_case_name, + request=camel_case_name_upper, + base_package=base_package, + dto_package=dto_package, + callback_package=callback_package, + callback=callback_type)) + methods_impl.append(method_impl_template.substitute(name=camel_case_name, + request=camel_case_name_upper, + base_package=base_package, + dto_package=dto_package, + callback_package=callback_package, + callback=callback_type)) + + join = os.path.join(callback_facade_package, "CallbackJVpp.java") + jvpp_file = open(join, 'w') + jvpp_file.write( + jvpp_ifc_template.substitute(methods="\n".join(methods), + base_package=base_package, + dto_package=dto_package, + callback_facade_package=callback_facade_package)) + jvpp_file.flush() + jvpp_file.close() + + jvpp_file = open(os.path.join(callback_facade_package, "CallbackJVppFacade.java"), 'w') + jvpp_file.write(jvpp_impl_template.substitute(methods="\n".join(methods_impl), + base_package=base_package, + dto_package=dto_package, + callback_package=callback_package, + callback_facade_package=callback_facade_package)) + jvpp_file.flush() + jvpp_file.close() + + generate_callback(func_list, base_package, dto_package, callback_package, callback_facade_package) + + +jvpp_facade_callback_template = Template(""" +package $base_package.$callback_facade_package; + +/** + * Async facade callback setting values to future objects + */ +public final class CallbackJVppFacadeCallback implements $base_package.$callback_package.JVppGlobalCallback { + + private final java.util.Map<Integer, $base_package.$callback_package.JVppCallback> requests; + + public CallbackJVppFacadeCallback(final java.util.Map<Integer, $base_package.$callback_package.JVppCallback> requestMap) { + this.requests = requestMap; + } + +$methods +} +""") + +jvpp_facade_callback_method_template = Template(""" + @Override + @SuppressWarnings("unchecked") + public void on$callback_dto($base_package.$dto_package.$callback_dto reply) { + + $base_package.$callback_package.$callback callback; + synchronized(requests) { + callback = ($base_package.$callback_package.$callback) requests.remove(reply.context); + } + + if(callback != null) { + callback.on$callback_dto(reply); + } + } +""") + + +def generate_callback(func_list, base_package, dto_package, callback_package, callback_facade_package): + callbacks = [] + for func in func_list: + + if util.is_notification(func['name']) or util.is_ignored(func['name']): + # TODO handle notifications + continue + + camel_case_name_with_suffix = util.underscore_to_camelcase_upper(func['name']) + if not util.is_reply(camel_case_name_with_suffix): + continue + + callbacks.append(jvpp_facade_callback_method_template.substitute(base_package=base_package, + dto_package=dto_package, + callback_package=callback_package, + callback=util.remove_reply_suffix(camel_case_name_with_suffix) + callback_gen.callback_suffix, + callback_dto=camel_case_name_with_suffix)) + + jvpp_file = open(os.path.join(callback_facade_package, "CallbackJVppFacadeCallback.java"), 'w') + jvpp_file.write(jvpp_facade_callback_template.substitute(base_package=base_package, + dto_package=dto_package, + callback_package=callback_package, + methods="".join(callbacks), + callback_facade_package=callback_facade_package)) + jvpp_file.flush() + jvpp_file.close() + + +# Returns request name or special one from unconventional_naming_rep_req map +def get_request_name(camel_case_dto_name, func_name): + if func_name in reverse_dict(util.unconventional_naming_rep_req): + request_name = util.underscore_to_camelcase_upper(reverse_dict(util.unconventional_naming_rep_req)[func_name]) + else: + request_name = camel_case_dto_name + return remove_suffix(request_name) + + +def reverse_dict(map): + return dict((v, k) for k, v in map.iteritems()) + + +def remove_suffix(name): + if util.is_reply(name): + return util.remove_reply_suffix(name) + else: + if util.is_dump(name): + return util.remove_suffix(name, util.dump_suffix) + else: + return name diff --git a/vpp-api/java/jvpp/gen/jvpp_future_facade_gen.py b/vpp-api/java/jvpp/gen/jvpp_future_facade_gen.py new file mode 100644 index 00000000..2aae5b80 --- /dev/null +++ b/vpp-api/java/jvpp/gen/jvpp_future_facade_gen.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python +# +# Copyright (c) 2016 Cisco and/or its affiliates. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os, util +from string import Template + +import dto_gen + +jvpp_facade_callback_template = Template(""" +package $base_package.$future_package; + +/** + * Async facade callback setting values to future objects + */ +public final class FutureJVppFacadeCallback implements $base_package.$callback_package.JVppGlobalCallback { + + private final java.util.Map<java.lang.Integer, java.util.concurrent.CompletableFuture<? extends $base_package.$dto_package.JVppReply<?>>> requests; + + public FutureJVppFacadeCallback(final java.util.Map<java.lang.Integer, java.util.concurrent.CompletableFuture<? extends $base_package.$dto_package.JVppReply<?>>> requestMap) { + this.requests = requestMap; + } + +$methods +} +""") + +jvpp_facade_callback_method_template = Template(""" + @Override + @SuppressWarnings("unchecked") + public void on$callback_dto($base_package.$dto_package.$callback_dto reply) { + final java.util.concurrent.CompletableFuture<$base_package.$dto_package.JVppReply<?>> completableFuture; + + synchronized(requests) { + completableFuture = (java.util.concurrent.CompletableFuture<$base_package.$dto_package.JVppReply<?>>) requests.get(reply.context); + } + + if(completableFuture != null) { + if(reply.retval < 0) { + completableFuture.completeExceptionally(new Exception("Invocation of " + $base_package.$dto_package.$callback_dto.class + + " failed with value " + reply.retval)); + } else { + completableFuture.complete(reply); + } + + synchronized(requests) { + requests.remove(reply.context); + } + } + } +""") + +# TODO reuse common parts with generic method callback +jvpp_facade_control_ping_method_template = Template(""" + @Override + @SuppressWarnings("unchecked") + public void on$callback_dto($base_package.$dto_package.$callback_dto reply) { + final java.util.concurrent.CompletableFuture<$base_package.$dto_package.JVppReply<?>> completableFuture; + + synchronized(requests) { + completableFuture = (java.util.concurrent.CompletableFuture<$base_package.$dto_package.JVppReply<?>>) requests.get(reply.context); + } + + if(completableFuture != null) { + // Finish dump call + if (completableFuture instanceof $base_package.$future_package.FutureJVppFacade.CompletableDumpFuture) { + completableFuture.complete((($base_package.$future_package.FutureJVppFacade.CompletableDumpFuture) completableFuture).getReplyDump()); + // Remove future mapped to dump call context id + synchronized(requests) { + requests.remove((($base_package.$future_package.FutureJVppFacade.CompletableDumpFuture) completableFuture).getContextId()); + } + } else { + if(reply.retval < 0) { + completableFuture.completeExceptionally(new Exception("Invocation of " + $base_package.$dto_package.$callback_dto.class + + " failed with value " + reply.retval)); + } else { + completableFuture.complete(reply); + } + } + + synchronized(requests) { + requests.remove(reply.context); + } + } + } +""") + +jvpp_facade_details_callback_method_template = Template(""" + @Override + @SuppressWarnings("unchecked") + public void on$callback_dto($base_package.$dto_package.$callback_dto reply) { + final FutureJVppFacade.CompletableDumpFuture<$base_package.$dto_package.$callback_dto_reply_dump> completableFuture; + + synchronized(requests) { + completableFuture = ($base_package.$future_package.FutureJVppFacade.CompletableDumpFuture<$base_package.$dto_package.$callback_dto_reply_dump>) requests.get(reply.context); + } + + if(completableFuture != null) { + $base_package.$dto_package.$callback_dto_reply_dump replyDump = completableFuture.getReplyDump(); + if(replyDump == null) { + replyDump = new $base_package.$dto_package.$callback_dto_reply_dump(); + completableFuture.setReplyDump(replyDump); + } + + replyDump.$callback_dto_field.add(reply); + } + } +""") + + +def generate_jvpp(func_list, base_package, dto_package, callback_package, future_facade_package): + """ Generates JVpp interface and JNI implementation """ + print "Generating JVpp future facade" + + if not os.path.exists(future_facade_package): + raise Exception("%s folder is missing" % future_facade_package) + + callbacks = [] + for func in func_list: + + if util.is_notification(func['name']) or util.is_ignored(func['name']): + # TODO handle notifications + continue + + camel_case_name_with_suffix = util.underscore_to_camelcase_upper(func['name']) + if not util.is_reply(camel_case_name_with_suffix): + continue + + if util.is_details(camel_case_name_with_suffix): + camel_case_method_name = util.underscore_to_camelcase(func['name']) + camel_case_request_name = get_standard_dump_reply_name(util.underscore_to_camelcase_upper(func['name']), + func['name']) + callbacks.append(jvpp_facade_details_callback_method_template.substitute(base_package=base_package, + dto_package=dto_package, + callback_dto=camel_case_name_with_suffix, + callback_dto_field=camel_case_method_name, + callback_dto_reply_dump=camel_case_request_name + dto_gen.dump_dto_suffix, + future_package=future_facade_package)) + else: + if util.is_control_ping(camel_case_name_with_suffix): + callbacks.append(jvpp_facade_control_ping_method_template.substitute(base_package=base_package, + dto_package=dto_package, + callback_dto=camel_case_name_with_suffix, + future_package=future_facade_package)) + else: + callbacks.append(jvpp_facade_callback_method_template.substitute(base_package=base_package, + dto_package=dto_package, + callback_dto=camel_case_name_with_suffix)) + + jvpp_file = open(os.path.join(future_facade_package, "FutureJVppFacadeCallback.java"), 'w') + jvpp_file.write(jvpp_facade_callback_template.substitute(base_package=base_package, + dto_package=dto_package, + callback_package=callback_package, + methods="".join(callbacks), + future_package=future_facade_package)) + jvpp_file.flush() + jvpp_file.close() + + +# Returns request name or special one from unconventional_naming_rep_req map +def get_standard_dump_reply_name(camel_case_dto_name, func_name): + # FIXME this is a hotfix for sub-details callbacks + # FIXME also for L2FibTableEntry + # It's all because unclear mapping between + # request -> reply, + # dump -> reply, details, + # notification_start -> reply, notifications + + # vpe.api needs to be "standardized" so we can parse the information and create maps before generating java code + suffix = func_name.split("_")[-1] + return util.underscore_to_camelcase_upper( + util.unconventional_naming_rep_req[func_name]) + util.underscore_to_camelcase_upper(suffix) if func_name in util.unconventional_naming_rep_req \ + else camel_case_dto_name diff --git a/vpp-api/java/jvpp/gen/jvpp_impl_gen.py b/vpp-api/java/jvpp/gen/jvpp_impl_gen.py new file mode 100644 index 00000000..5446a694 --- /dev/null +++ b/vpp-api/java/jvpp/gen/jvpp_impl_gen.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python +# +# Copyright (c) 2016 Cisco and/or its affiliates. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os, util +from string import Template + +jvpp_ifc_template = Template(""" +package $base_package; + +public interface JVpp extends java.lang.AutoCloseable { + + /** + * Generic dispatch method for sending requests to VPP + */ + int send($base_package.$dto_package.JVppRequest request); + + @Override + void close(); + +$methods +} +""") + +jvpp_impl_template = Template(""" +package $base_package; + +public final class JVppImpl implements $base_package.JVpp { + + private final $base_package.VppConnection connection; + + public JVppImpl(final $base_package.VppConnection connection) { + if(connection == null) { + throw new java.lang.NullPointerException("Connection is null"); + } + this.connection = connection; + } + + @Override + public void close() { + connection.close(); + } + + @Override + public int send($base_package.$dto_package.JVppRequest request) { + return request.send(this); + } + +$methods +} +""") + +method_template = Template(""" int $name($base_package.$dto_package.$request request);""") +method_native_template = Template( + """ private static native int ${name}0($base_package.$dto_package.$request request);""") +method_impl_template = Template(""" public final int $name($base_package.$dto_package.$request request) { + if(request == null) { + throw new java.lang.NullPointerException("Null request object"); + } + connection.checkActive(); + return ${name}0(request); + } +""") + +no_arg_method_template = Template(""" int $name();""") +no_arg_method_native_template = Template(""" private static native int ${name}0();""") +no_arg_method_impl_template = Template(""" public final int $name() { + connection.checkActive(); + return ${name}0(); + } +""") + + +def generate_jvpp(func_list, base_package, dto_package): + """ Generates JVpp interface and JNI implementation """ + print "Generating JVpp" + + methods = [] + methods_impl = [] + for func in func_list: + + if util.is_notification(func['name']) or util.is_ignored(func['name']): + # TODO handle notifications + continue + + camel_case_name = util.underscore_to_camelcase(func['name']) + camel_case_name_upper = util.underscore_to_camelcase_upper(func['name']) + if util.is_reply(camel_case_name): + continue + + if len(func['args']) == 0: + methods.append(no_arg_method_template.substitute(name=camel_case_name, + base_package=base_package, + dto_package=dto_package)) + methods_impl.append( + no_arg_method_native_template.substitute(name=camel_case_name, + base_package=base_package, + dto_package=dto_package)) + methods_impl.append(no_arg_method_impl_template.substitute(name=camel_case_name, + base_package=base_package, + dto_package=dto_package)) + else: + methods.append(method_template.substitute(name=camel_case_name, + request=camel_case_name_upper, + base_package=base_package, + dto_package=dto_package)) + methods_impl.append(method_native_template.substitute(name=camel_case_name, + request=camel_case_name_upper, + base_package=base_package, + dto_package=dto_package)) + methods_impl.append(method_impl_template.substitute(name=camel_case_name, + request=camel_case_name_upper, + base_package=base_package, + dto_package=dto_package)) + + jvpp_file = open("JVpp.java", 'w') + jvpp_file.write( + jvpp_ifc_template.substitute(methods="\n".join(methods), + base_package=base_package, + dto_package=dto_package)) + jvpp_file.flush() + jvpp_file.close() + + jvpp_file = open("JVppImpl.java", 'w') + jvpp_file.write(jvpp_impl_template.substitute(methods="\n".join(methods_impl), + base_package=base_package, + dto_package=dto_package)) + jvpp_file.flush() + jvpp_file.close() diff --git a/vpp-api/java/jvpp/gen/util.py b/vpp-api/java/jvpp/gen/util.py new file mode 100644 index 00000000..17dc2ed9 --- /dev/null +++ b/vpp-api/java/jvpp/gen/util.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python +# +# Copyright (c) 2016 Cisco and/or its affiliates. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +from os import removedirs + + +def underscore_to_camelcase(name): + name = name.title().replace("_", "") + return name[0].lower() + name[1:] + + +def underscore_to_camelcase_upper(name): + name = name.title().replace("_", "") + return name[0].upper() + name[1:] + + +def remove_folder(folder): + """ Remove folder with all its files """ + for root, dirs, files in os.walk(folder, topdown=False): + for name in files: + os.remove(os.path.join(root, name)) + removedirs(folder) + + +reply_suffixes = ("reply", "details", "l2fibtableentry") + + +def is_reply(name): + return name.lower().endswith(reply_suffixes) + + +def is_details(name): + return name.lower().endswith(reply_suffixes[1]) or name.lower().endswith(reply_suffixes[2]) + +dump_suffix = "dump" + + +def is_dump(name): + return name.lower().endswith(dump_suffix) + + +def get_reply_suffix(name): + for reply_suffix in reply_suffixes: + if name.lower().endswith(reply_suffix): + if reply_suffix == reply_suffixes[2]: + # FIXME workaround for l2_fib_table_entry + return 'entry' + else: + return reply_suffix + +# http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html +jni_2_java_type_mapping = {'jbyte': 'byte', + 'jbyteArray': 'byte[]', + 'jchar': 'char', + 'jcharArray': 'char[]', + 'jshort': 'short', + 'jshortArray': 'short[]', + 'jint': 'int', + 'jintArray': 'int[]', + 'jlong': 'long', + 'jlongArray': 'long[]', + 'jdouble': 'double', + 'jdoubleArray': 'double[]', + 'jfloat': 'float', + 'jfloatArray': 'float[]', + 'void': 'void', + 'jstring': 'java.lang.String', + 'jobject': 'java.lang.Object', + 'jobjectArray': 'java.lang.Object[]' + } + +# https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/types.html#type_signatures +jni_2_signature_mapping = {'jbyte': 'B', + 'jbyteArray': '[B', + 'jchar': 'C', + 'jcharArray': '[C', + 'jshort': 'S', + 'jshortArray': '[S', + 'jint': 'I', + 'jintArray': '[I', + 'jlong': 'J', + 'jlongArray': '[J', + 'jdouble': 'D', + 'jdoubleArray': '[D', + 'jfloat': 'F', + 'jfloatArray': '[F' + } + +# https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#Get_type_Field_routines +jni_field_accessors = { + 'jbyte': 'ByteField', + 'jbyteArray': 'ObjectField', + 'jchar': 'CharField', + 'jcharArray': 'ObjectField', + 'jshort': 'ShortField', + 'jshortArray': 'ObjectField', + 'jint': 'IntField', + 'jintArray': 'ObjectField', + 'jlong': 'LongField', + 'jlongArray': 'ObjectField', + 'jdouble': 'DoubleField', + 'jdoubleArray': 'ObjectField', + 'jfloat': 'FloatField', + 'jfloatArray': 'ObjectField' +} + +# TODO watch out for unsigned types +# http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html +vpp_2_jni_type_mapping = {'u8': 'jbyte', # fixme + 'i8': 'jbyte', + 'u16': 'jchar', + 'i16': 'jshort', + 'u32': 'jint', # fixme + 'i32': 'jint', + 'u64': 'jlong', # fixme + 'i64': 'jlong', + 'f64': 'jdouble' + } + +# vpe.api calls that do not follow naming conventions and have to be handled exceptionally when finding reply -> request mapping +# FIXME in vpe.api +unconventional_naming_rep_req = { + 'cli_reply': 'cli_request', + 'vnet_summary_stats_reply': 'vnet_get_summary_stats', + # This below is actually a sub-details callback. We cannot derive the mapping of dump request + # belonging to this sub-details from naming conventions. We need special mapping + 'bridge_domain_sw_if_details': 'bridge_domain', + # This is standard dump call + details reply. However it's not called details but entry + 'l2_fib_table_entry': 'l2_fib_table' + } + +# +# FIXME no convention in the naming of events (notifications) in vpe.api +notifications_message_suffixes = ("event", "counters") +notification_messages = ["from_netconf_client", "from_netconf_server", "to_netconf_client", "to_netconf_server"] + +# messages that must be ignored. These messages are INSUFFICIENTLY marked as disabled in vpe.api +# FIXME +ignored_messages = ["is_address_reachable"] + + +def is_notification(param): + return param.lower().endswith(notifications_message_suffixes) or param.lower() in notification_messages + + +def is_ignored(param): + return param.lower() in ignored_messages + + +def remove_reply_suffix(camel_case_name_with_suffix): + return remove_suffix(camel_case_name_with_suffix, get_reply_suffix(camel_case_name_with_suffix)) + + +def remove_suffix(camel_case_name_with_suffix, suffix): + suffix_length = len(suffix) + return camel_case_name_with_suffix[:-suffix_length] if suffix_length != 0 else camel_case_name_with_suffix + + +def is_control_ping(camel_case_name_with_suffix): + return "controlping" in camel_case_name_with_suffix.lower() diff --git a/vpp-api/java/jvpp/org/openvpp/jvpp/VppConnection.java b/vpp-api/java/jvpp/org/openvpp/jvpp/VppConnection.java new file mode 100644 index 00000000..72ff62c9 --- /dev/null +++ b/vpp-api/java/jvpp/org/openvpp/jvpp/VppConnection.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed 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.openvpp.jvpp; + +/** + * Representation of a management connection to VPP. + * Connection is initiated when instance is created, closed with close(). + */ +public interface VppConnection extends AutoCloseable { + + /** + * Check if this instance connection is active. + * + * @throws IllegalStateException if this instance was disconnected. + */ + void checkActive(); + + /** + * Closes Vpp connection. + */ + @Override + void close(); +} diff --git a/vpp-api/java/jvpp/org/openvpp/jvpp/VppJNIConnection.java b/vpp-api/java/jvpp/org/openvpp/jvpp/VppJNIConnection.java new file mode 100644 index 00000000..9e07cc76 --- /dev/null +++ b/vpp-api/java/jvpp/org/openvpp/jvpp/VppJNIConnection.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed 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.openvpp.jvpp; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.logging.Logger; +import org.openvpp.jvpp.callback.JVppCallback; + +/** + * JNI based representation of a management connection to VPP + */ +public final class VppJNIConnection implements VppConnection { + private final static Logger LOG = Logger.getLogger(VppJNIConnection.class.getName()); + private static final String LIBNAME = "libjvpp.so.0.0.0"; + + static { + try { + loadLibrary(); + } catch (Exception e) { + LOG.severe("Can't find vpp jni library: " + LIBNAME); + throw new ExceptionInInitializerError(e); + } + } + + private static void loadStream(final InputStream is) throws IOException { + final Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rwxr-x---"); + final Path p = Files.createTempFile(LIBNAME, null, PosixFilePermissions.asFileAttribute(perms)); + try { + Files.copy(is, p, StandardCopyOption.REPLACE_EXISTING); + + try { + Runtime.getRuntime().load(p.toString()); + } catch (UnsatisfiedLinkError e) { + throw new IOException("Failed to load library " + p, e); + } + } finally { + try { + Files.deleteIfExists(p); + } catch (IOException e) { + } + } + } + + private static void loadLibrary() throws IOException { + try (final InputStream is = VppJNIConnection.class.getResourceAsStream('/' + LIBNAME)) { + if (is == null) { + throw new IOException("Failed to open library resource " + LIBNAME); + } + loadStream(is); + } + } + + private final String clientName; + private volatile boolean disconnected = false; + + private VppJNIConnection(final String clientName) { + if (clientName == null) { + throw new NullPointerException("Null clientName"); + } + this.clientName = clientName; + } + + /** + * Guarded by VppJNIConnection.class + */ + private static final Map<String, VppJNIConnection> connections = new HashMap<>(); + + /** + * Create a new Vpp connection identified by clientName parameter. + * + * Multiple instances are allowed since this class is not a singleton + * (VPP allows multiple management connections). + * + * However only a single connection per clientName is allowed. + * + * @param clientName identifier of vpp connection + * @param callback global callback to receive response calls from vpp + * + * @return new Vpp connection + * @throws IOException in case the connection could not be established, or there already is a connection with the same name + */ + public static VppJNIConnection create(final String clientName, final JVppCallback callback) throws IOException { + synchronized (VppJNIConnection.class) { + if(connections.containsKey(clientName)) { + throw new IOException("Client " + clientName + " already connected"); + } + + final VppJNIConnection vppJNIConnection = new VppJNIConnection(clientName); + final int ret = clientConnect(clientName, callback); + if (ret != 0) { + throw new IOException("Connection returned error " + ret); + } + connections.put(clientName, vppJNIConnection); + return vppJNIConnection; + } + } + + @Override + public final void checkActive() { + if (disconnected) { + throw new IllegalStateException("Disconnected client " + clientName); + } + } + + @Override + public synchronized final void close() { + if (!disconnected) { + disconnected = true; + try { + clientDisconnect(); + } finally { + synchronized (VppJNIConnection.class) { + connections.remove(clientName); + } + } + } + } + + private static native int clientConnect(String clientName, JVppCallback callback); + private static native void clientDisconnect(); +} diff --git a/vpp-api/java/jvpp/org/openvpp/jvpp/callback/JVppCallback.java b/vpp-api/java/jvpp/org/openvpp/jvpp/callback/JVppCallback.java new file mode 100644 index 00000000..c17f2e0a --- /dev/null +++ b/vpp-api/java/jvpp/org/openvpp/jvpp/callback/JVppCallback.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed 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.openvpp.jvpp.callback; + +/** +* Base JVppCallback interface +*/ +public interface JVppCallback { +} diff --git a/vpp-api/java/jvpp/org/openvpp/jvpp/dto/JVppDump.java b/vpp-api/java/jvpp/org/openvpp/jvpp/dto/JVppDump.java new file mode 100644 index 00000000..295bbba8 --- /dev/null +++ b/vpp-api/java/jvpp/org/openvpp/jvpp/dto/JVppDump.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed 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.openvpp.jvpp.dto; + +/** +* Base interface for all dump requests +*/ +public interface JVppDump extends JVppRequest { + +} diff --git a/vpp-api/java/jvpp/org/openvpp/jvpp/dto/JVppReply.java b/vpp-api/java/jvpp/org/openvpp/jvpp/dto/JVppReply.java new file mode 100644 index 00000000..2f4964c4 --- /dev/null +++ b/vpp-api/java/jvpp/org/openvpp/jvpp/dto/JVppReply.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed 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.openvpp.jvpp.dto; + +/** +* Base interface for all reply DTOs +*/ +public interface JVppReply<REQ extends JVppRequest> { + +} diff --git a/vpp-api/java/jvpp/org/openvpp/jvpp/dto/JVppReplyDump.java b/vpp-api/java/jvpp/org/openvpp/jvpp/dto/JVppReplyDump.java new file mode 100644 index 00000000..4aecedc1 --- /dev/null +++ b/vpp-api/java/jvpp/org/openvpp/jvpp/dto/JVppReplyDump.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed 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.openvpp.jvpp.dto; + +/** +* Base interface for all dump replies +*/ +public interface JVppReplyDump<REQ extends JVppRequest, RESP extends JVppReply<REQ>> + extends JVppReply<REQ> { + +} diff --git a/vpp-api/java/jvpp/org/openvpp/jvpp/dto/JVppRequest.java b/vpp-api/java/jvpp/org/openvpp/jvpp/dto/JVppRequest.java new file mode 100644 index 00000000..8cd1534a --- /dev/null +++ b/vpp-api/java/jvpp/org/openvpp/jvpp/dto/JVppRequest.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed 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.openvpp.jvpp.dto; + +import org.openvpp.jvpp.JVpp; + +/** +* Base interface for all request DTOs +*/ +public interface JVppRequest { + + /** + * Invoke current operation asynchronously on VPP + * + * @return context id of this request. Can be used to track incomming response + */ + int send(JVpp jvpp); + +} diff --git a/vpp-api/java/jvpp/org/openvpp/jvpp/future/FutureJVpp.java b/vpp-api/java/jvpp/org/openvpp/jvpp/future/FutureJVpp.java new file mode 100644 index 00000000..d860bc2d --- /dev/null +++ b/vpp-api/java/jvpp/org/openvpp/jvpp/future/FutureJVpp.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed 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.openvpp.jvpp.future; + + +import java.util.concurrent.CompletionStage; +import org.openvpp.jvpp.dto.JVppReply; +import org.openvpp.jvpp.dto.JVppRequest; + +/** +* Future facade on top of JVpp +*/ +public interface FutureJVpp { + + /** + * Invoke asynchronous operation on VPP + * + * @return CompletionStage with future result of an async VPP call + */ + <REQ extends JVppRequest, REPLY extends JVppReply<REQ>> CompletionStage<REPLY> send(REQ req); + +} diff --git a/vpp-api/java/jvpp/org/openvpp/jvpp/future/FutureJVppFacade.java b/vpp-api/java/jvpp/org/openvpp/jvpp/future/FutureJVppFacade.java new file mode 100644 index 00000000..5b8222c4 --- /dev/null +++ b/vpp-api/java/jvpp/org/openvpp/jvpp/future/FutureJVppFacade.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed 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.openvpp.jvpp.future; + + +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import org.openvpp.jvpp.JVpp; +import org.openvpp.jvpp.dto.ControlPing; +import org.openvpp.jvpp.dto.JVppDump; +import org.openvpp.jvpp.dto.JVppReply; +import org.openvpp.jvpp.dto.JVppReplyDump; +import org.openvpp.jvpp.dto.JVppRequest; + +/** +* Future facade on top of JVpp +*/ +public final class FutureJVppFacade implements FutureJVpp { + + private final JVpp jvpp; + + /** + * Guarded by self + */ + private final Map<Integer, CompletableFuture<? extends JVppReply<?>>> requests; + + public FutureJVppFacade(final JVpp jvpp, + final Map<Integer, CompletableFuture<? extends JVppReply<?>>> requestMap) { + // TODO use guava's preconditions for nonNull and state checks + // However adding guava as a dependency requires better build system for Java in VPP project + // Currently it's just invocation of javac + if(jvpp == null) { + throw new NullPointerException("Null jvpp"); + } + this.jvpp = jvpp; + if(requestMap == null) { + throw new NullPointerException("Null requestMap"); + } + // Request map represents the shared state between this facade and it's callback + // where facade puts futures in and callback completes + removes them + // TODO what if the call never completes ? + this.requests = requestMap; + } + + // TODO use Optional in Future, java8 + + @Override + @SuppressWarnings("unchecked") + public <REQ extends JVppRequest, REPLY extends JVppReply<REQ>> CompletionStage<REPLY> send(REQ req) { + synchronized(requests) { + final int contextId = jvpp.send(req); + + final CompletableFuture<REPLY> replyCompletableFuture; + if(req instanceof JVppDump) { + replyCompletableFuture = (CompletableFuture<REPLY>) new CompletableDumpFuture<>(contextId); + } else { + replyCompletableFuture = new CompletableFuture<>(); + } + + requests.put(contextId, replyCompletableFuture); + if(req instanceof JVppDump) { + requests.put(jvpp.send(new ControlPing()), replyCompletableFuture); + } + return replyCompletableFuture; + } + } + + static final class CompletableDumpFuture<T extends JVppReplyDump<?, ?>> extends CompletableFuture<T> { + // TODO make this final + // The reason why this is not final is the instantiation of ReplyDump DTOs + // Their instantiation must be generated, so currently the DTOs are created in callback and set when first dump reponses + // is returned. Because in callback we have method per response, but here where requests are invoked there is only + // a single generic send method that does not have enough information to create the DTO + // This can be final as soon as we provide specific send methods here + private T replyDump; + private final long contextId; + + CompletableDumpFuture(final long contextId) { + this.contextId = contextId; + } + + long getContextId() { + return contextId; + } + + T getReplyDump() { + return replyDump; + } + + void setReplyDump(final T replyDump) { + this.replyDump = replyDump; + } + } + +} diff --git a/vpp-api/java/jvpp/org/openvpp/jvpp/test/CallbackApiTest.java b/vpp-api/java/jvpp/org/openvpp/jvpp/test/CallbackApiTest.java new file mode 100644 index 00000000..5ac4b69a --- /dev/null +++ b/vpp-api/java/jvpp/org/openvpp/jvpp/test/CallbackApiTest.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed 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.openvpp.jvpp.test; + +import org.openvpp.jvpp.JVpp; +import org.openvpp.jvpp.JVppImpl; +import org.openvpp.jvpp.VppJNIConnection; +import org.openvpp.jvpp.callback.GetNodeIndexCallback; +import org.openvpp.jvpp.callback.ShowVersionCallback; +import org.openvpp.jvpp.callback.SwInterfaceCallback; +import org.openvpp.jvpp.dto.GetNodeIndex; +import org.openvpp.jvpp.dto.GetNodeIndexReply; +import org.openvpp.jvpp.dto.ShowVersion; +import org.openvpp.jvpp.dto.ShowVersionReply; +import org.openvpp.jvpp.dto.SwInterfaceDetails; +import org.openvpp.jvpp.dto.SwInterfaceDump; + +public class CallbackApiTest { + + private static class TestCallback implements GetNodeIndexCallback, ShowVersionCallback, SwInterfaceCallback { + + @Override + public void onGetNodeIndexReply(final GetNodeIndexReply msg) { + System.out.printf("Received GetNodeIndexReply: context=%d, retval=%d, nodeIndex=%d\n", + msg.context, msg.retval, msg.nodeIndex); + } + @Override + public void onShowVersionReply(final ShowVersionReply msg) { + System.out.printf("Received ShowVersionReply: context=%d, retval=%d, program=%s, version=%s, " + + "buildDate=%s, buildDirectory=%s\n", + msg.context, msg.retval, new String(msg.program), new String(msg.version), + new String(msg.buildDate), new String(msg.buildDirectory)); + } + + @Override + public void onSwInterfaceDetails(final SwInterfaceDetails msg) { + System.out.printf("Received SwInterfaceDetails: interfaceName=%s, l2AddressLength=%d, adminUpDown=%d, " + + "linkUpDown=%d, linkSpeed=%d, linkMtu=%d\n", + new String(msg.interfaceName), msg.l2AddressLength, msg.adminUpDown, + msg.linkUpDown, msg.linkSpeed, (int)msg.linkMtu); + } + } + + private static void testCallbackApi() throws Exception { + System.out.println("Testing Java callback API"); + JVpp jvpp = new JVppImpl(VppJNIConnection.create("CallbackApiTest", new TestCallback())); + System.out.println("Successfully connected to VPP"); + + System.out.println("Sending ShowVersion request..."); + jvpp.send(new ShowVersion()); + + System.out.println("Sending GetNodeIndex request..."); + GetNodeIndex getNodeIndexRequest = new GetNodeIndex(); + getNodeIndexRequest.nodeName = "node0".getBytes(); + jvpp.send(getNodeIndexRequest); + + System.out.println("Sending SwInterfaceDump request..."); + SwInterfaceDump swInterfaceDumpRequest = new SwInterfaceDump(); + swInterfaceDumpRequest.nameFilterValid = 0; + swInterfaceDumpRequest.nameFilter = "".getBytes(); + jvpp.send(swInterfaceDumpRequest); + + Thread.sleep(5000); + + System.out.println("Disconnecting..."); + jvpp.close(); + Thread.sleep(1000); + } + + public static void main(String[] args) throws Exception { + testCallbackApi(); + } +} diff --git a/vpp-api/java/jvpp/org/openvpp/jvpp/test/CallbackJVppFacadeTest.java b/vpp-api/java/jvpp/org/openvpp/jvpp/test/CallbackJVppFacadeTest.java new file mode 100644 index 00000000..df3b0e70 --- /dev/null +++ b/vpp-api/java/jvpp/org/openvpp/jvpp/test/CallbackJVppFacadeTest.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed 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.openvpp.jvpp.test; + +import java.util.HashMap; +import java.util.Map; +import org.openvpp.jvpp.JVpp; +import org.openvpp.jvpp.JVppImpl; +import org.openvpp.jvpp.VppJNIConnection; +import org.openvpp.jvpp.callback.JVppCallback; +import org.openvpp.jvpp.callback.ShowVersionCallback; +import org.openvpp.jvpp.callfacade.CallbackJVppFacade; +import org.openvpp.jvpp.callfacade.CallbackJVppFacadeCallback; + +/** + * CallbackJVppFacade together with CallbackJVppFacadeCallback allow for setting different callback for each request. + * This is more convenient than the approach shown in CallbackApiTest. + */ +public class CallbackJVppFacadeTest { + + private static ShowVersionCallback showVersionCallback1 = msg -> + System.out.printf("ShowVersionCallback1 received ShowVersionReply: context=%d, retval=%d, program=%s," + + "version=%s, buildDate=%s, buildDirectory=%s\n", msg.context, msg.retval, new String(msg.program), + new String(msg.version), new String(msg.buildDate), new String(msg.buildDirectory)); + + private static ShowVersionCallback showVersionCallback2 = msg -> + System.out.printf("ShowVersionCallback2 received ShowVersionReply: context=%d, retval=%d, program=%s," + + "version=%s, buildDate=%s, buildDirectory=%s\n", msg.context, msg.retval, new String(msg.program), + new String(msg.version), new String(msg.buildDate), new String(msg.buildDirectory)); + + private static void testCallbackFacade() throws Exception { + System.out.println("Testing CallbackJVppFacade"); + + final Map<Integer, JVppCallback> callbackMap = new HashMap<>(); + JVpp impl = new JVppImpl(VppJNIConnection.create("CallbackApiTest", new CallbackJVppFacadeCallback(callbackMap))); + CallbackJVppFacade jvpp = new CallbackJVppFacade(impl, callbackMap); + System.out.println("Successfully connected to VPP"); + + jvpp.showVersion(showVersionCallback1); + jvpp.showVersion(showVersionCallback2); + + + Thread.sleep(2000); + + System.out.println("Disconnecting..."); + impl.close(); + Thread.sleep(1000); + } + + public static void main(String[] args) throws Exception { + testCallbackFacade(); + } +} diff --git a/vpp-api/java/jvpp/org/openvpp/jvpp/test/ControlPingTest.java b/vpp-api/java/jvpp/org/openvpp/jvpp/test/ControlPingTest.java new file mode 100644 index 00000000..f1bd41de --- /dev/null +++ b/vpp-api/java/jvpp/org/openvpp/jvpp/test/ControlPingTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed 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.openvpp.jvpp.test; + +import org.openvpp.jvpp.JVpp; +import org.openvpp.jvpp.JVppImpl; +import org.openvpp.jvpp.VppJNIConnection; +import org.openvpp.jvpp.callback.ControlPingCallback; +import org.openvpp.jvpp.dto.ControlPing; +import org.openvpp.jvpp.dto.ControlPingReply; + +public class ControlPingTest { + + private static void testControlPing() throws Exception { + System.out.println("Testing ControlPing using Java callback API"); + + JVpp jvpp = new JVppImpl(VppJNIConnection.create("ControlPingTest", new ControlPingCallback() { + @Override + public void onControlPingReply(final ControlPingReply reply) { + System.out.printf("Received ControlPingReply: context=%d, retval=%d, clientIndex=%d vpePid=%d\n", + reply.context, reply.retval, reply.clientIndex, reply.vpePid); + } + })); + System.out.println("Successfully connected to VPP"); + Thread.sleep(1000); + + jvpp.send(new ControlPing()); + + Thread.sleep(2000); + + System.out.println("Disconnecting..."); + jvpp.close(); + Thread.sleep(1000); + } + + public static void main(String[] args) throws Exception { + testControlPing(); + } +} diff --git a/vpp-api/java/jvpp/org/openvpp/jvpp/test/FutureApiTest.java b/vpp-api/java/jvpp/org/openvpp/jvpp/test/FutureApiTest.java new file mode 100644 index 00000000..b5b36d58 --- /dev/null +++ b/vpp-api/java/jvpp/org/openvpp/jvpp/test/FutureApiTest.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed 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.openvpp.jvpp.test; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; +import org.openvpp.jvpp.VppJNIConnection; +import org.openvpp.jvpp.dto.GetNodeIndex; +import org.openvpp.jvpp.dto.GetNodeIndexReply; +import org.openvpp.jvpp.dto.JVppReply; +import org.openvpp.jvpp.dto.ShowVersion; +import org.openvpp.jvpp.dto.ShowVersionReply; +import org.openvpp.jvpp.dto.SwInterfaceDetails; +import org.openvpp.jvpp.dto.SwInterfaceDetailsReplyDump; +import org.openvpp.jvpp.dto.SwInterfaceDump; +import org.openvpp.jvpp.future.FutureJVppFacade; +import org.openvpp.jvpp.future.FutureJVppFacadeCallback; + +public class FutureApiTest { + + private static void testShowVersion(final FutureJVppFacade jvpp) { + System.out.println("Sending ShowVersion request..."); + try { + final Future<JVppReply<ShowVersion>> replyFuture = jvpp.send(new ShowVersion()).toCompletableFuture(); + final ShowVersionReply reply = (ShowVersionReply) replyFuture.get(); // TODO can we get rid of that cast? + System.out.printf("Received ShowVersionReply: context=%d, retval=%d, program=%s, " + + "version=%s, buildDate=%s, buildDirectory=%s\n", + reply.context, reply.retval, new String(reply.program), new String(reply.version), + new String(reply.buildDate), new String(reply.buildDirectory)); + } catch (Exception e) { + System.err.printf("ShowVersion request failed:\n"); + e.printStackTrace(); + } + } + + /** + * This test will fail with some error code if node 'node0' is not defined. + * TODO: consider adding error messages specific for given api calls + */ + private static void testGetNodeIndex(final FutureJVppFacade jvpp) { + System.out.println("Sending GetNodeIndex request..."); + try { + final GetNodeIndex request = new GetNodeIndex(); + request.nodeName = "node0".getBytes(); + final Future<JVppReply<GetNodeIndex>> replyFuture = jvpp.send(request).toCompletableFuture(); + final GetNodeIndexReply reply = (GetNodeIndexReply) replyFuture.get(); + System.out.printf("Received GetNodeIndexReply: context=%d, retval=%d, nodeIndex=%d\n", + reply.context, reply.retval, reply.nodeIndex); + } catch (Exception e) { + System.err.printf("GetNodeIndex request failed:\n"); + e.printStackTrace(); + } + } + + private static void testSwInterfaceDump(final FutureJVppFacade jvpp) { + System.out.println("Sending SwInterfaceDump request..."); + try { + final SwInterfaceDump request = new SwInterfaceDump(); + request.nameFilterValid = 0; + request.nameFilter = "".getBytes(); + final Future<JVppReply<SwInterfaceDump>> replyFuture = jvpp.send(request).toCompletableFuture(); + final SwInterfaceDetailsReplyDump reply = (SwInterfaceDetailsReplyDump) replyFuture.get(); + + if (reply == null) { + throw new IllegalStateException("SwInterfaceDetailsReplyDump is null!"); + } + if (reply.swInterfaceDetails == null) { + throw new IllegalStateException("SwInterfaceDetailsReplyDump.swInterfaceDetails is null!"); + } + + for (SwInterfaceDetails details : reply.swInterfaceDetails) { + if (details == null) { + throw new IllegalStateException("reply.swInterfaceDetails contains null element!"); + } + + System.out.printf("Received SwInterfaceDetails: interfaceName=%s, l2AddressLength=%d, adminUpDown=%d, " + + "linkUpDown=%d, linkSpeed=%d, linkMtu=%d\n", + new String(details.interfaceName), details.l2AddressLength, details.adminUpDown, + details.linkUpDown, details.linkSpeed, (int) details.linkMtu); + } + } catch (Exception e) { + System.err.printf("SwInterfaceDump request failed:\n"); + e.printStackTrace(); + } + } + + private static void testFutureApi() throws Exception { + System.out.println("Testing Java future API"); + + final Map<Integer, CompletableFuture<? extends JVppReply<?>>> map = new HashMap<>(); + final org.openvpp.jvpp.JVppImpl impl = + new org.openvpp.jvpp.JVppImpl(VppJNIConnection.create("FutureApiTest", new FutureJVppFacadeCallback(map))); + final FutureJVppFacade jvpp = new FutureJVppFacade(impl, map); + System.out.println("Successfully connected to VPP"); + + testShowVersion(jvpp); + testGetNodeIndex(jvpp); + testSwInterfaceDump(jvpp); + + System.out.println("Disconnecting..."); + // TODO we should consider adding jvpp.close(); to the facade + impl.close(); + } + + public static void main(String[] args) throws Exception { + testFutureApi(); + } +} |