You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@celix.apache.org by pn...@apache.org on 2019/12/10 22:35:41 UTC

[celix] branch develop updated: #129: Fixes an issue with stopping a bundle twice using the 'stop' shell command.

This is an automated email from the ASF dual-hosted git repository.

pnoltes pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/celix.git


The following commit(s) were added to refs/heads/develop by this push:
     new 3239a05  #129: Fixes an issue with stopping a bundle twice using the 'stop' shell command.
3239a05 is described below

commit 3239a050c2b6920cd286397208dc122e9ebaef40
Author: Pepijn Noltes <pe...@gmail.com>
AuthorDate: Tue Dec 10 23:35:21 2019 +0100

    #129: Fixes an issue with stopping a bundle twice using the 'stop' shell command.
---
 bundles/shell/shell/src/install_command.c          |  44 ++-----
 bundles/shell/shell/src/start_command.c            |  81 +++++--------
 bundles/shell/shell/src/std_commands.h             |   4 +-
 bundles/shell/shell/src/stop_command.c             |  81 +++++--------
 bundles/shell/shell/src/uninstall_command.c        |  25 ++--
 libs/dfi/include/dyn_type.h                        | 127 ++++++++++++++++++++-
 libs/dfi/src/dyn_type.c                            |  10 ++
 libs/dfi/test/dyn_type_tests.cpp                   |  14 +++
 libs/framework/include/celix_bundle_context.h      |   8 ++
 libs/framework/src/bundle_context.c                |   5 +
 .../framework/tst/bundle_context_bundles_tests.cpp |  55 ++++++++-
 11 files changed, 288 insertions(+), 166 deletions(-)

diff --git a/bundles/shell/shell/src/install_command.c b/bundles/shell/shell/src/install_command.c
index 16d6182..48370ef 100644
--- a/bundles/shell/shell/src/install_command.c
+++ b/bundles/shell/shell/src/install_command.c
@@ -16,27 +16,22 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-/**
- * install_command.c
- *
- *  \date       Apr 4, 2011
- *  \author    	<a href="mailto:dev@celix.apache.org">Apache Celix Project Team</a>
- *  \copyright	Apache License, Version 2.0
- */
 
 #include <stdlib.h>
 #include <string.h>
+#include "celix_api.h"
 
-#include "array_list.h"
-#include "bundle_context.h"
+static void printInstalledBundle(void *handle, const celix_bundle_t *bnd) {
+    FILE *outStream = handle;
+    fprintf(outStream, "Bundle '%s' installed with bundle id %li\n", celix_bundle_getSymbolicName(bnd), celix_bundle_getId(bnd));
+}
 
 void installCommand_execute(void *handle, char * line, FILE *outStream, FILE *errStream) {
-	bundle_context_pt context = handle;
+	celix_bundle_context_t *ctx = handle;
 
 	char delims[] = " ";
 	char * sub = NULL;
 	char *save_ptr = NULL;
-	char info[256];
 
 	// ignore the command
 	sub = strtok_r(line, delims, &save_ptr);
@@ -45,33 +40,12 @@ void installCommand_execute(void *handle, char * line, FILE *outStream, FILE *er
 	if (sub == NULL) {
 		fprintf(errStream, "Incorrect number of arguments.\n");
 	} else {
-		info[0] = '\0';
 		while (sub != NULL) {
-			bundle_pt bundle = NULL;
-			bundleContext_installBundle(context, sub, &bundle);
-			if (bundle != NULL) {
-				long id;
-				bundle_archive_pt archive = NULL;
-				char bundleId[sizeof(id) + 1];
-
-				if (strlen(info) > 0) {
-					strcat(info, ", ");
-				}
-				bundle_getArchive(bundle, &archive);
-				bundleArchive_getId(archive, &id);
-				sprintf(bundleId, "%ld", id);
-				strcat(info, bundleId);
+		    long bndId = celix_bundleContext_installBundle(ctx, sub, true);
+			if (bndId >= 0) {
+			    celix_bundleContext_useBundle(ctx, bndId, outStream, printInstalledBundle);
 			}
 			sub = strtok_r(NULL, delims, &save_ptr);
 		}
-		if (strchr(info, ',') != NULL) {
-			fprintf(outStream, "Bundle IDs: ");
-			fprintf(outStream, "%s", info);
-			fprintf(outStream, "\n");
-		} else if (strlen(info) > 0) {
-			fprintf(outStream, "Bundle ID: ");
-			fprintf(outStream, "%s", info);
-			fprintf(outStream, "\n");
-		}
 	}
 }
\ No newline at end of file
diff --git a/bundles/shell/shell/src/start_command.c b/bundles/shell/shell/src/start_command.c
index 91d0b04..1bbcd92 100644
--- a/bundles/shell/shell/src/start_command.c
+++ b/bundles/shell/shell/src/start_command.c
@@ -16,70 +16,43 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-/**
- * start_command.c
- *
- *  \date       Aug 20, 2010
- *  \author    	<a href="mailto:dev@celix.apache.org">Apache Celix Project Team</a>
- *  \copyright	Apache License, Version 2.0
- */
 
 #include <stdlib.h>
 #include <string.h>
 #include <stdio.h>
 
+#include "celix_api.h"
 #include "std_commands.h"
-#include "bundle_context.h"
-
-celix_status_t startCommand_execute(void *_ptr, char *command_line_str, FILE *out_ptr, FILE *err_ptr) {
-    celix_status_t status = CELIX_SUCCESS;
-
-    bundle_context_pt context_ptr = _ptr;
-
-    if (!context_ptr || !command_line_str || !out_ptr || !err_ptr) {
-        status = CELIX_ILLEGAL_ARGUMENT;
-    }
-
-    if (status == CELIX_SUCCESS) {
-        char *sub_str = NULL;
-        char *save_ptr = NULL;
-
-        strtok_r(command_line_str, OSGI_SHELL_COMMAND_SEPARATOR, &save_ptr);
-        sub_str = strtok_r(NULL, OSGI_SHELL_COMMAND_SEPARATOR, &save_ptr);
-
-        if (sub_str == NULL) {
-            fprintf(out_ptr, "Incorrect number of arguments.\n");
-        } else {
-            while (sub_str != NULL) {
-                celix_status_t sub_status = CELIX_SUCCESS;
-
-                bundle_pt bundle_ptr = NULL;
 
-                char *end_str = NULL;
-                long id = strtol(sub_str, &end_str, 10);
-                if (*end_str) {
-                    sub_status = CELIX_ILLEGAL_ARGUMENT;
-                    fprintf(err_ptr, "Bundle id '%s' is invalid, problem at %s\n", sub_str, end_str);
+celix_status_t startCommand_execute(void *handle, char *command, FILE *outStream, FILE *errStream) {
+    celix_bundle_context_t *ctx = handle;
+
+    char *sub_str = NULL;
+    char *save_ptr = NULL;
+
+    strtok_r(command, OSGI_SHELL_COMMAND_SEPARATOR, &save_ptr);
+    sub_str = strtok_r(NULL, OSGI_SHELL_COMMAND_SEPARATOR, &save_ptr);
+
+    if (sub_str == NULL) {
+        fprintf(outStream, "Incorrect number of arguments.\n");
+    } else {
+        while (sub_str != NULL) {
+            char *end_str = NULL;
+            long bndId = strtol(sub_str, &end_str, 10);
+            if (*end_str) {
+                fprintf(errStream, "Bundle id '%s' is invalid, problem at %s\n", sub_str, end_str);
+            } else {
+                bool exists = celix_bundleContext_isBundleInstalled(ctx, bndId);
+                if (exists) {
+                    celix_bundleContext_startBundle(ctx, bndId);
+                } else {
+                    fprintf(outStream, "No bundle with id %li.\n", bndId);
                 }
-
-                if (sub_status == CELIX_SUCCESS) {
-                    sub_status = bundleContext_getBundleById(context_ptr, id, &bundle_ptr);
-                }
-
-                if (sub_status == CELIX_SUCCESS) {
-                    bundle_startWithOptions(bundle_ptr, 0);
-                }
-
-                if (sub_status != CELIX_SUCCESS) {
-                    status = sub_status;
-                    break;
-                }
-
-                sub_str = strtok_r(NULL, OSGI_SHELL_COMMAND_SEPARATOR, &save_ptr);
             }
-        }
 
+            sub_str = strtok_r(NULL, OSGI_SHELL_COMMAND_SEPARATOR, &save_ptr);
+        }
     }
 
-    return status;
+    return CELIX_SUCCESS;
 }
diff --git a/bundles/shell/shell/src/std_commands.h b/bundles/shell/shell/src/std_commands.h
index 0568897..a0c66b4 100644
--- a/bundles/shell/shell/src/std_commands.h
+++ b/bundles/shell/shell/src/std_commands.h
@@ -32,8 +32,8 @@
 #define OSGI_SHELL_COMMAND_SEPARATOR " "
 
 celix_status_t lbCommand_execute(void *_ptr, char *command_line_str, FILE *out_ptr, FILE *err_ptr);
-celix_status_t startCommand_execute(void *_ptr, char *command_line_str, FILE *out_ptr, FILE *err_ptr);
-celix_status_t stopCommand_execute(void *handle, char * commandline, FILE *outStream, FILE *errStream);
+celix_status_t startCommand_execute(void *_ptr, char* command_line_str, FILE *out_ptr, FILE *err_ptr);
+celix_status_t stopCommand_execute(void *handle, char* commandline, FILE *outStream, FILE *errStream);
 celix_status_t installCommand_execute(void *handle, char * commandline, FILE *outStream, FILE *errStream);
 celix_status_t uninstallCommand_execute(void *handle, char * commandline, FILE *outStream, FILE *errStream);
 celix_status_t updateCommand_execute(void *handle, char * commandline, FILE *outStream, FILE *errStream);
diff --git a/bundles/shell/shell/src/stop_command.c b/bundles/shell/shell/src/stop_command.c
index 4578e7a..687b8ef 100644
--- a/bundles/shell/shell/src/stop_command.c
+++ b/bundles/shell/shell/src/stop_command.c
@@ -16,68 +16,43 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-/**
- * stop_command.c
- *
- *  \date       Aug 20, 2010
- *  \author    	<a href="mailto:dev@celix.apache.org">Apache Celix Project Team</a>
- *  \copyright	Apache License, Version 2.0
- */
 
 #include <stdlib.h>
 #include <string.h>
 
-#include "bundle_context.h"
+#include "celix_api.h"
 #include "std_commands.h"
 
-celix_status_t stopCommand_execute(void *_ptr, char *command_line_str, FILE *out_ptr, FILE *err_ptr) {
-    celix_status_t status = CELIX_SUCCESS;
-
-    bundle_context_pt context_ptr = _ptr;
-
-    if (!context_ptr || !command_line_str || !out_ptr || !err_ptr) {
-        status = CELIX_ILLEGAL_ARGUMENT;
-    }
-
-    if (status == CELIX_SUCCESS) {
-        char *sub_str = NULL;
-        char *save_ptr = NULL;
-
-        strtok_r(command_line_str, OSGI_SHELL_COMMAND_SEPARATOR, &save_ptr);
-        sub_str = strtok_r(NULL, OSGI_SHELL_COMMAND_SEPARATOR, &save_ptr);
-
-        if (sub_str == NULL) {
-            fprintf(out_ptr, "Incorrect number of arguments.\n");
-        } else {
-            while (sub_str != NULL) {
-                celix_status_t sub_status = CELIX_SUCCESS;
-
-                bundle_pt bundle_ptr = NULL;
-
-                char *end_str = NULL;
-                long id = strtol(sub_str, &end_str, 10);
-                if (*end_str) {
-                    sub_status = CELIX_ILLEGAL_ARGUMENT;
-                    fprintf(err_ptr, "Bundle id '%s' is invalid, problem at %s\n", sub_str, end_str);
-                }
-
-                if (sub_status == CELIX_SUCCESS) {
-                    sub_status = bundleContext_getBundleById(context_ptr, id, &bundle_ptr);
-                }
-
-                if (sub_status == CELIX_SUCCESS) {
-                    bundle_stopWithOptions(bundle_ptr, 0);
+celix_status_t stopCommand_execute(void *handle, char *command, FILE *outStream, FILE *errStream) {
+    celix_bundle_context_t *ctx = handle;
+
+    char *sub_str = NULL;
+    char *save_ptr = NULL;
+
+    strtok_r(command, OSGI_SHELL_COMMAND_SEPARATOR, &save_ptr);
+    sub_str = strtok_r(NULL, OSGI_SHELL_COMMAND_SEPARATOR, &save_ptr);
+
+    if (sub_str == NULL) {
+        fprintf(outStream, "Incorrect number of arguments.\n");
+    } else {
+        while (sub_str != NULL) {
+
+            char *end_str = NULL;
+            long bndId = strtol(sub_str, &end_str, 10);
+            if (*end_str) {
+                fprintf(errStream, "Bundle id '%s' is invalid, problem at %s\n", sub_str, end_str);
+            } else {
+                bool exists = celix_bundleContext_isBundleInstalled(ctx, bndId);
+                if (exists) {
+                    celix_bundleContext_stopBundle(ctx, bndId);
+                } else {
+                    fprintf(outStream, "No bundle with id %li.\n", bndId);
                 }
-
-                if (sub_status != CELIX_SUCCESS) {
-                    status = sub_status;
-                    break;
-                }
-
-                sub_str = strtok_r(NULL, OSGI_SHELL_COMMAND_SEPARATOR, &save_ptr);
             }
+
+            sub_str = strtok_r(NULL, OSGI_SHELL_COMMAND_SEPARATOR, &save_ptr);
         }
     }
 
-    return status;
+    return CELIX_SUCCESS;
 }
diff --git a/bundles/shell/shell/src/uninstall_command.c b/bundles/shell/shell/src/uninstall_command.c
index 8998b42..ea1ad79 100644
--- a/bundles/shell/shell/src/uninstall_command.c
+++ b/bundles/shell/shell/src/uninstall_command.c
@@ -27,33 +27,32 @@
 #include <stdlib.h>
 #include <string.h>
 
-#include "array_list.h"
-#include "bundle_context.h"
+#include "celix_api.h"
 
-celix_status_t uninstallCommand_execute(void *handle, char * line, FILE *outStream, FILE *errStream) {
-	bundle_context_pt context = handle;
+
+celix_status_t uninstallCommand_execute(void *handle, char* command, FILE *outStream, FILE *errStream) {
+	celix_bundle_context_t *ctx = handle;
 	char delims[] = " ";
 	char * sub = NULL;
 	char *save_ptr = NULL;
 	celix_status_t status = CELIX_SUCCESS;
 
-	sub = strtok_r(line, delims, &save_ptr);
+	sub = strtok_r(command, delims, &save_ptr);
 	sub = strtok_r(NULL, delims, &save_ptr);
 
 	if (sub == NULL) {
 		fprintf(errStream, "Incorrect number of arguments.\n");
 	} else {
 		while (sub != NULL) {
-			long id = atol(sub);
-			bundle_pt bundle = NULL;
-			status = bundleContext_getBundleById(context, id, &bundle);
-			if (status==CELIX_SUCCESS && bundle!=NULL) {
-				bundle_uninstall(bundle);
+			long bndId = atol(sub);
+			bool exists = celix_bundleContext_isBundleInstalled(ctx, bndId);
+			if (exists) {
+			    celix_bundleContext_uninstallBundle(ctx, bndId);
 			} else {
-				fprintf(errStream, "Bundle id is invalid.");
-			}
+                fprintf(outStream, "No bundle with id %li.\n", bndId);
+            }
 			sub = strtok_r(NULL, delims, &save_ptr);
 		}
 	}
 	return status;
-}
+}
\ No newline at end of file
diff --git a/libs/dfi/include/dyn_type.h b/libs/dfi/include/dyn_type.h
index 127c3ee..500764b 100644
--- a/libs/dfi/include/dyn_type.h
+++ b/libs/dfi/include/dyn_type.h
@@ -33,8 +33,15 @@
 #include "memstream/fmemopen.h"
 #endif
 
-/* Description string
+/**
+ * dyn type (dynamic type) represent a structure in memory. It can be used to calculate the needed memory size, the size
+ * and offset of struct members and can be used to dynamically alloc, initialize, dealloc, read & write structure
+ * in memory. In other words it can be used for reflection.
  *
+ * Dyn types can be created by parsing a dyn type descriptor string.
+ * Dyn type descriptor strings are very compact description of a C structure.
+ *
+ * Descriptor strings grammar:
  * Type = [TypeDef]* (MetaInfo)* (SimpleType | ComplexType | SequenceType | TypedPointer | PointerReference ) [TypeDef]*
  * Name = alpha[(alpha|numeric)*]
  * SPACE = ' ' 
@@ -128,28 +135,136 @@ struct meta_entry {
 DFI_SETUP_LOG_HEADER(dynType);
 DFI_SETUP_LOG_HEADER(dynAvprType);
 
-//generic
+/**
+ * Parses a descriptor stream and creates a dyn_type (dynamic type).
+ * If successful the type output argument points to the newly created dyn type.
+ * The caller is the owner of the dyn type and use dynType_destroy deallocate the memory.
+ *
+ * @param descriptorStream  Stream to the descriptor.
+ * @param name              name for the dyn_type. This can be used in references to dyn types.
+ * @param refTypes          A list if reference-able dyn types.
+ * @param type              Output argument for the parsed dyn type.
+ * @return                  0 if successful.
+ */
 int dynType_parse(FILE *descriptorStream, const char *name, struct types_head *refTypes, dyn_type **type);
+
+/**
+ * Parses a descriptor string and creates a dyn_type (dynamic type).
+ * If successful the type output argument points to the newly created dyn type.
+ * The caller is the owner of the dyn type and use dynType_destroy deallocate the memory.
+ *
+ * @param descriptor        The descriptor.
+ * @param name              name for the dyn_type. This can be used in references to dyn types.
+ * @param refTypes          A list if reference-able dyn types.
+ * @param type              Output argument for the parsed dyn type.
+ * @return                  0 if successful.
+ */
 int dynType_parseWithStr(const char *descriptor, const char *name, struct types_head *refTypes, dyn_type **type);
+
+/**
+ * Destroy a dyn type and de-allocates the memory.
+ * @param type      The dyn type to destroy.
+ */
 void dynType_destroy(dyn_type *type);
 
-int dynType_alloc(dyn_type *type, void **bufLoc);
-void dynType_free(dyn_type *type, void *loc);
+/**
+ * Allocates memory for a type instance described by a dyn type. The memory will be 0 allocated (calloc).
+
+ * @param type      The dyn type for which structure to allocate.
+ * @param instance  The output argument for the allocated memory.
+ * @return          0 on success.
+ */
+int dynType_alloc(dyn_type *type, void **instance);
 
+/**
+ * free the memory for a type instance described by a dyn type.
+ * This is a deep free.
+ *
+ * @param type        The dyn type for which structure to allocate.
+ * @param instance    The memory location of the type instance.
+ */
+void dynType_free(dyn_type *type, void *instance);
+
+/**
+ * Prints the dyn type information to the provided output stream.
+ * @param type      The dyn type to print.
+ * @param stream    The output stream (e.g. stdout).
+ */
 void dynType_print(dyn_type *type, FILE *stream);
+
+/**
+ * Return the size of the structure descripbed by the dyn type.
+ * This this _not_ includes sizes of data where is only pointed to (i.e. content of sequences, strings, etc).
+ *
+ * @param type  The dyn type.
+ * @return      The size of the type instance described by dyn type.
+ */
 size_t dynType_size(dyn_type *type);
+
+/**
+ * The type of the dyn type
+ * E.g. DYN_TYPE_SIMPLE, DYN_TYPE_COMPLEX, etc
+ * @param type  The dyn type
+ * @return      The type of the dyn type.
+ */
 int dynType_type(dyn_type *type);
+
+/**
+ * Returns the char identifier of the dyn type type.
+ * E.g. 'D' for a double, or '{' for a complex type.
+ * @param type  The dyn type
+ * @return      The descriptor of the dyn type.
+ */
 int dynType_descriptorType(dyn_type *type);
+
+/**
+ * Get the dyn type meta information for the provided name.
+ * @param type  The dyn type.
+ * @param name  The name of the requested meta information.
+ * @return      The meta information or NULL if the meta information is not present.
+ */
 const char * dynType_getMetaInfo(dyn_type *type, const char *name);
+
+/**
+ * Returns a list of meta entries.
+ * Note that dyn type is owner of the entries. Traversing the entries is not thread safe.
+ * @param type      The dyn type.
+ * @param entries   The output arguments for the meta entries.
+ * @return          0 on success.
+ */
 int dynType_metaEntries(dyn_type *type, struct meta_properties_head **entries);
+
+/**
+ * Returns the name of the dyn type. Name can be NULL.
+ */
 const char * dynType_getName(dyn_type *type);
 
-//complexType
+
+/**
+ * Returns the field index for a complex type.
+ *
+ * e.g. for a struct { int a; int b; }, a will have an index of 0 and b an index of 1.
+ * @param type  The dyn type. Must be a complex type.
+ * @param name  The field name.
+ * @return      The field index or -1 if no field with the name was found.
+ */
 int dynType_complex_indexForName(dyn_type *type, const char *name);
+
+/**
+ * Returns the field dyn type for a given complex type and a field index.
+ *
+ * @param type      The dyn type. Must be a complex type.
+ * @param index     The field index for which field the dyn type should be returned. The field index must be correct.
+ * @param subType   The field dyn type as output.
+ *                  exists.
+ * @return          0 if successful.
+ */
 int dynType_complex_dynTypeAt(dyn_type *type, int index, dyn_type **subType);
+
 int dynType_complex_setValueAt(dyn_type *type, int index, void *inst, void *in);
 int dynType_complex_valLocAt(dyn_type *type, int index, void *inst, void **valLoc);
 int dynType_complex_entries(dyn_type *type, struct complex_type_entries_head **entries);
+size_t dynType_complex_nrOfEntries(dyn_type *type);
 
 //sequence
 int dynType_sequence_alloc(dyn_type *type, void *inst, uint32_t cap);
@@ -171,4 +286,4 @@ void dynType_simple_setValue(dyn_type *type, void *inst, void *in);
 dyn_type * dynType_parseAvpr(FILE *avprStream, const char *fqn);
 dyn_type * dynType_parseAvprWithStr(const char *avpr, const char *fqn);
 
-#endif
+#endif //_DYN_TYPE_H_
diff --git a/libs/dfi/src/dyn_type.c b/libs/dfi/src/dyn_type.c
index a3657bf..89a1fa9 100644
--- a/libs/dfi/src/dyn_type.c
+++ b/libs/dfi/src/dyn_type.c
@@ -24,6 +24,7 @@
 #include <assert.h>
 #include <errno.h>
 #include <ffi.h>
+#include <dyn_type_common.h>
 
 #include "dyn_type_common.h"
 #include "dyn_common.h"
@@ -638,6 +639,15 @@ int dynType_complex_valLocAt(dyn_type *type, int index, void *inst, void **resul
     return OK;
 }
 
+size_t dynType_complex_nrOfEntries(dyn_type *type) {
+    size_t count = 0;
+    struct complex_type_entry *entry = NULL;
+    TAILQ_FOREACH(entry, &type->complex.entriesHead, entries) {
+        ++count;
+    }
+    return count;
+}
+
 int dynType_complex_entries(dyn_type *type, struct complex_type_entries_head **entries) {
     assert(type->type == DYN_TYPE_COMPLEX);
     int status = OK;
diff --git a/libs/dfi/test/dyn_type_tests.cpp b/libs/dfi/test/dyn_type_tests.cpp
index 7b358a7..e1c2145 100644
--- a/libs/dfi/test/dyn_type_tests.cpp
+++ b/libs/dfi/test/dyn_type_tests.cpp
@@ -307,3 +307,17 @@ TEST(DynTypeTests, EnumTest) {
     dynType_print(type, stdout);
     dynType_destroy(type);
 }
+
+TEST(DynTypeTests, NrOfEntriesTest) {
+    dyn_type *type = NULL;
+    int rc = dynType_parseWithStr("{DD}", NULL, NULL, &type);
+    CHECK_EQUAL(0, rc);
+    CHECK_EQUAL(2, dynType_complex_nrOfEntries(type));
+    dynType_destroy(type);
+
+    type = NULL;
+    rc = dynType_parseWithStr("{DDJJ}", NULL, NULL, &type);
+    CHECK_EQUAL(0, rc);
+    CHECK_EQUAL(4, dynType_complex_nrOfEntries(type));
+    dynType_destroy(type);
+}
\ No newline at end of file
diff --git a/libs/framework/include/celix_bundle_context.h b/libs/framework/include/celix_bundle_context.h
index 779c134..18c44c0 100644
--- a/libs/framework/include/celix_bundle_context.h
+++ b/libs/framework/include/celix_bundle_context.h
@@ -585,6 +585,14 @@ void celix_bundleContext_useServicesWithOptions(
  */
 celix_array_list_t* celix_bundleContext_listBundles(celix_bundle_context_t *ctx);
 
+/**
+ * Check if whether a bundle is installed.
+ * @param ctx       The bundle context.
+ * @param bndId     The bundle id to check
+ * @return          true if the bundle is installed.
+ */
+bool celix_bundleContext_isBundleInstalled(celix_bundle_context_t *ctx, long bndId);
+
 
 /**
  * Install and optional start a bundle.
diff --git a/libs/framework/src/bundle_context.c b/libs/framework/src/bundle_context.c
index dd76ba1..430781c 100644
--- a/libs/framework/src/bundle_context.c
+++ b/libs/framework/src/bundle_context.c
@@ -712,6 +712,11 @@ celix_array_list_t* celix_bundleContext_listBundles(celix_bundle_context_t *ctx)
     return result;
 }
 
+bool celix_bundleContext_isBundleInstalled(celix_bundle_context_t *ctx, long bndId) {
+    celix_bundle_t *bnd = framework_getBundleById(ctx->framework, bndId);
+    return bnd != NULL;
+}
+
 static void bundleContext_startBundleCallback(void *handle, const bundle_t *c_bnd) {
     bool *started = handle;
     *started = false;
diff --git a/libs/framework/tst/bundle_context_bundles_tests.cpp b/libs/framework/tst/bundle_context_bundles_tests.cpp
index 8f462d9..d492875 100644
--- a/libs/framework/tst/bundle_context_bundles_tests.cpp
+++ b/libs/framework/tst/bundle_context_bundles_tests.cpp
@@ -139,9 +139,14 @@ TEST(CelixBundleContextBundlesTests, useBundleTest) {
 };
 
 TEST(CelixBundleContextBundlesTests, StopStartTest) {
-    celix_bundleContext_installBundle(ctx, TEST_BND1_LOC, true);
-    celix_bundleContext_installBundle(ctx, TEST_BND2_LOC, true);
-    celix_bundleContext_installBundle(ctx, TEST_BND3_LOC, true);
+    long bndId1 = celix_bundleContext_installBundle(ctx, TEST_BND1_LOC, true);
+    long bndId2 = celix_bundleContext_installBundle(ctx, TEST_BND2_LOC, true);
+    long bndId3 = celix_bundleContext_installBundle(ctx, TEST_BND3_LOC, true);
+    CHECK_TRUE(celix_bundleContext_isBundleInstalled(ctx, bndId1));
+    CHECK_TRUE(celix_bundleContext_isBundleInstalled(ctx, bndId2));
+    CHECK_TRUE(celix_bundleContext_isBundleInstalled(ctx, bndId3));
+    CHECK_FALSE(celix_bundleContext_isBundleInstalled(ctx, 600 /*non existing*/));
+
 
 
     celix_array_list_t *ids = celix_bundleContext_listBundles(ctx);
@@ -184,6 +189,50 @@ TEST(CelixBundleContextBundlesTests, StopStartTest) {
     celix_arrayList_destroy(ids);
 }
 
+TEST(CelixBundleContextBundlesTests, DoubleStopTest) {
+    long bndId = celix_bundleContext_installBundle(ctx, TEST_BND1_LOC, true);
+    CHECK_TRUE(bndId > 0);
+    CHECK_TRUE(celix_bundleContext_isBundleInstalled(ctx, bndId));
+
+    //TODO rewrite using celix_bundleContext_useBundlesWithOptions ....
+    bool called = celix_framework_useBundle(fw, false, bndId, nullptr, [](void *, const celix_bundle_t *bnd) {
+        CHECK_EQUAL(OSGI_FRAMEWORK_BUNDLE_ACTIVE, celix_bundle_getState(bnd));
+    });
+    CHECK_TRUE(called);
+
+    //first
+    celix_bundleContext_stopBundle(ctx, bndId);
+
+    called = celix_framework_useBundle(fw, false, bndId, nullptr, [](void *, const celix_bundle_t *bnd) {
+        CHECK_EQUAL(OSGI_FRAMEWORK_BUNDLE_RESOLVED, celix_bundle_getState(bnd));
+    });
+    CHECK_TRUE(called);
+
+    //second
+    celix_bundleContext_stopBundle(ctx, bndId);
+
+    called = celix_framework_useBundle(fw, false, bndId, nullptr, [](void *, const celix_bundle_t *bnd) {
+        CHECK_EQUAL(OSGI_FRAMEWORK_BUNDLE_RESOLVED, celix_bundle_getState(bnd));
+    });
+    CHECK_TRUE(called);
+
+    //first
+    celix_bundleContext_startBundle(ctx, bndId);
+
+    called = celix_framework_useBundle(fw, false, bndId, nullptr, [](void *, const celix_bundle_t *bnd) {
+        CHECK_EQUAL(OSGI_FRAMEWORK_BUNDLE_ACTIVE, celix_bundle_getState(bnd));
+    });
+    CHECK_TRUE(called);
+
+    //second
+    celix_bundleContext_startBundle(ctx, bndId);
+
+    called = celix_framework_useBundle(fw, false, bndId, nullptr, [](void *, const celix_bundle_t *bnd) {
+        CHECK_EQUAL(OSGI_FRAMEWORK_BUNDLE_ACTIVE, celix_bundle_getState(bnd));
+    });
+    CHECK_TRUE(called);
+}
+
 TEST(CelixBundleContextBundlesTests, trackBundlesTest) {
     struct data {
         int count{0};