sdedic commented on code in PR #7413:
URL: https://github.com/apache/netbeans/pull/7413#discussion_r1624090607


##########
enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/AddNewAssetCommand.java:
##########
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.cloud.oracle.assets;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import org.netbeans.api.project.Project;
+import org.netbeans.api.project.ProjectUtils;
+import org.netbeans.modules.cloud.oracle.assets.Steps.ItemTypeStep;
+import org.netbeans.modules.cloud.oracle.assets.Steps.ProjectStep;
+import org.netbeans.modules.cloud.oracle.assets.Steps.SuggestedContext;
+import org.netbeans.modules.cloud.oracle.items.OCIItem;
+import org.netbeans.modules.project.dependency.ArtifactSpec;
+import org.netbeans.modules.project.dependency.Dependency;
+import org.netbeans.modules.project.dependency.DependencyChange;
+import org.netbeans.modules.project.dependency.DependencyChangeException;
+import org.netbeans.modules.project.dependency.ProjectDependencies;
+import org.netbeans.modules.project.dependency.ProjectOperationException;
+import org.netbeans.modules.refactoring.spi.ModificationResult;
+import org.netbeans.modules.project.dependency.Scope;
+import org.netbeans.spi.lsp.CommandProvider;
+import org.openide.util.Exceptions;
+import org.openide.util.Lookup;
+import org.openide.util.NbBundle;
+import org.openide.util.Pair;
+import org.openide.util.lookup.Lookups;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author Jan Horvath
+ */
[email protected]({
+    "NoProjects=No Project Found",
+    "SelectProject=Select Project to Update Dependencies",
+    "SelectResourceType=Select Resource Type"})
+@ServiceProvider(service = CommandProvider.class)
+public class AddNewAssetCommand implements CommandProvider {
+
+    private static final String COMMAND_ADD_NEW_ASSET = 
"nbls.cloud.assets.add.new"; //NOI18N
+
+    private static final Set COMMANDS = new HashSet<>(Arrays.asList(
+            COMMAND_ADD_NEW_ASSET
+    ));
+
+    private static final Map<String, String[]> DEP_MAP = new HashMap() {
+        {
+            put("Databases", new String[]{"io.micronaut.oraclecloud", 
"micronaut-oraclecloud-atp"}); //NOI18N
+            put("Bucket", new String[]{"io.micronaut.objectstorage", 
"micronaut-object-storage-oracle-cloud"}); //NOI18N
+            put("Vault", new String[]{"io.micronaut.oraclecloud", 
"micronaut-oraclecloud-vault"}); //NOI18N
+        }
+    };
+
+    @Override
+    public Set<String> getCommands() {
+        return Collections.unmodifiableSet(COMMANDS);
+    }
+
+    @Override
+    public CompletableFuture<Object> runCommand(String command, List<Object> 
arguments) {
+        CompletableFuture future = new CompletableFuture();
+        NewSuggestedContext context = new 
NewSuggestedContext(OpenProjectsFinder.getDefault().findTopLevelProjects());
+        Lookup lookup = Lookups.fixed(context);
+        Steps.getDefault()
+                .executeMultistep(new ItemTypeStep(), lookup)
+                .thenAccept(result -> {
+                    Project project = ((Pair<Project, OCIItem>) 
result).first();
+                    OCIItem item = ((Pair<Project, OCIItem>) result).second();
+                    CloudAssets.getDefault().addItem(item);
+                    Project projectToModify = null;
+                    Set<Project> subProjects = 
ProjectUtils.getContainedProjects(project, false);
+                    for (Project subProject : subProjects) {
+                        if 
("oci".equals(subProject.getProjectDirectory().getName())) { //NOI18N
+                            projectToModify = subProject;
+                            break;
+                        }
+                    }
+                    if (projectToModify == null) {
+                        projectToModify = project;
+                    }
+                    if (projectToModify != null) {
+                        String[] art = DEP_MAP.get(context.getItemType());
+                        ArtifactSpec spec = ArtifactSpec.make(art[0], art[1]);
+                        Dependency dep = Dependency.make(spec, 
Scope.named("implementation")); //NOI18N
+                        DependencyChange change = 
DependencyChange.add(Collections.singletonList(dep), 
DependencyChange.Options.skipConflicts);
+                        try {
+                            ModificationResult mod = 
ProjectDependencies.modifyDependencies(projectToModify, change);
+                            mod.commit();
+                        } catch (IOException ex) {
+                            future.completeExceptionally(ex);
+                        } catch (DependencyChangeException ex) {
+                            future.completeExceptionally(ex);
+                        } catch (ProjectOperationException ex) {
+                            future.completeExceptionally(ex);
+                        }
+                    }
+                });
+        future.complete(null);
+        return future;
+    }
+
+    private final class NewSuggestedContext implements SuggestedContext {
+
+        private final CompletableFuture projects;

Review Comment:
    Missing generic type, pls. have strong typing.



##########
enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/AddSuggestedItemAction.java:
##########
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.cloud.oracle.assets;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.logging.Logger;
+import org.netbeans.api.db.explorer.ConnectionManager;
+import org.netbeans.api.db.explorer.DatabaseConnection;
+import org.netbeans.modules.cloud.oracle.actions.AddADBAction;
+import org.netbeans.modules.cloud.oracle.assets.Steps.DatabaseConnectionStep;
+import org.netbeans.modules.cloud.oracle.assets.Steps.TenancyStep;
+import org.netbeans.modules.cloud.oracle.database.DatabaseItem;
+import org.netbeans.modules.cloud.oracle.items.OCID;
+import org.netbeans.modules.cloud.oracle.items.OCIItem;
+import org.openide.awt.ActionID;
+import org.openide.awt.ActionRegistration;
+import org.openide.util.Lookup;
+import org.openide.util.NbBundle;
+import org.openide.util.lookup.Lookups;
+
+/**
+ *
+ * @author Jan Horvath
+ */
+@ActionID(
+        category = "Tools",
+        id = "org.netbeans.modules.cloud.oracle.actions.AddSuggestedItemAction"
+)
+@ActionRegistration(
+        displayName = "#AddSuggestedItem",
+        asynchronous = true
+)
+
[email protected]({
+    "AddSuggestedItem=Add Suggested Oracle Cloud Resource",
+    "SelectProfile=Select OCI Profile",
+    "# {0} - tenancy name",
+    "# {1} - region id",
+    "SelectProfile_Description={0} (region: {1})",
+    "SelectCompartment=Select Compartment",
+    "NoProfile=There is not any OCI profile in the config",
+    "NoCompartment=There are no compartments in the Tenancy",
+    "CollectingProfiles=Searching for OCI Profiles",
+    "CollectingProfiles_Text=Loading OCI Profiles",
+    "CollectingItems=Loading OCI contents",
+    "CollectingItems_Text=Listing compartments and databases",
+    "SelectItem=Select {0}",
+    "SelectDBConnection=Select Database Connection"
+})
+public class AddSuggestedItemAction implements ActionListener {
+
+    private static final Logger LOG = 
Logger.getLogger(AddADBAction.class.getName());
+
+    private final SuggestedItem context;
+
+    public AddSuggestedItemAction(SuggestedItem context) {
+        this.context = context;
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        if ("Databases".equals(context.getPath())) { //NOI18N
+            Map<String, DatabaseItem> adbConnections = new HashMap<>();
+            DatabaseConnection[] connections = 
ConnectionManager.getDefault().getConnections();
+            for (int i = 0; i < connections.length; i++) {
+                String name = connections[i].getDisplayName();
+                String ocid = 
connections[i].getConnectionProperties().getProperty("OCID"); //NOI18N
+                String compartmentId = 
connections[i].getConnectionProperties().getProperty("CompartmentOCID"); 
//NOI18N
+                if (ocid != null && compartmentId != null) {
+                    DatabaseItem dbItem = 
+                            new DatabaseItem(OCID.of(ocid, "Databases"), 
compartmentId, name, null, name); //NOI18N
+                    adbConnections.put(name, dbItem);

Review Comment:
   Corner case: can user create 2 `DatabaseConnections` with the same (display) 
name ? 



##########
enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/CloudAssets.java:
##########
@@ -0,0 +1,279 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.cloud.oracle.assets;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonIOException;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonParser;
+import com.google.gson.JsonSyntaxException;
+import com.google.gson.stream.JsonReader;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.StringReader;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.swing.event.ChangeListener;
+import org.netbeans.api.db.explorer.ConnectionListener;
+import org.netbeans.api.db.explorer.ConnectionManager;
+import org.netbeans.api.db.explorer.DatabaseConnection;
+import org.netbeans.modules.cloud.oracle.bucket.BucketItem;
+import org.netbeans.modules.cloud.oracle.compute.ClusterItem;
+import org.netbeans.modules.cloud.oracle.compute.ComputeInstanceItem;
+import org.netbeans.modules.cloud.oracle.database.DatabaseItem;
+import org.netbeans.modules.cloud.oracle.items.OCID;
+import org.netbeans.modules.cloud.oracle.items.OCIItem;
+import org.netbeans.modules.cloud.oracle.vault.VaultItem;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.modules.OnStart;
+import org.openide.util.ChangeSupport;
+import org.openide.util.Exceptions;
+
+/**
+ *
+ * @author Jan Horvath
+ */
+public class CloudAssets {
+
+    private static final String CLOUD_ASSETS_PATH = "CloudAssets"; //NOI18N
+    private static final String CLOUD_ASSETS_FILE = "default.json"; //NOI18N
+    private static CloudAssets instance = null;
+
+    private boolean assetsLoaded = false;
+    private Set<OCIItem> items = new HashSet<>();
+
+    private final ChangeSupport changeSupport;
+    private final Gson gson;
+
+    CloudAssets() {
+        this.gson = new GsonBuilder()
+                .setPrettyPrinting()
+                .registerTypeAdapter(OCID.class, new OCIDDeserializer())
+                .create();
+        changeSupport = new ChangeSupport(this);
+        ConnectionManager.getDefault().addConnectionListener(() -> {
+            DatabaseConnection[] connections = 
ConnectionManager.getDefault().getConnections();
+            Set<String> ocids = new HashSet<>();
+            for (int i = 0; i < connections.length; i++) {
+                ocids.add((String) 
connections[i].getConnectionProperties().get("OCID")); //NOI18N
+            }
+            boolean update = false;
+            for (Iterator it = items.iterator(); it.hasNext();) {
+                OCIItem item = (OCIItem) it.next();
+                if (!ocids.contains(item.getKey().getValue()) && 
"Databases".equals(item.getKey().getPath())) { //NOI18N
+                    it.remove();
+                    update = true;
+                }
+            }
+            if (update) {
+                update();
+            }
+        });
+    }
+
+    public static synchronized CloudAssets getDefault() {
+        if (instance == null) {
+            instance = new CloudAssets();
+        }
+        return instance;
+    }
+
+    @OnStart
+    public static final class Init implements Runnable {
+
+        @Override
+        public void run() {
+            CloudAssets.getDefault().loadAssets();
+        }
+
+    }
+
+    public void addItem(OCIItem newItem) {
+        items.add(newItem);
+        update();
+    }
+
+    void removeItem(OCIItem item) {
+        if (items.remove(item)) {
+            update();
+        }
+    }
+
+    public void update() {
+        OpenProjectsFinder.getDefault().findOpenProjects().thenAccept(projects 
-> {
+            SuggestionAnalyzer analyzer = new DependenciesAnalyzer();
+            Set<SuggestedItem> suggested = analyzer.findSuggestions(projects);
+            setSuggestions(suggested);
+        });
+    }
+
+    public synchronized void setSuggestions(Set<SuggestedItem> suggested) {
+        for (OCIItem item : new HashSet<OCIItem>(items)) {
+            if (item.getKey().getPath().equals("Suggested")) { //NOI18N
+                items.remove(item);
+            }
+        }
+        Set<String> present = items.stream()
+                .map(i -> i.getKey().getPath())
+                .collect(Collectors.toSet());
+        for (SuggestedItem s : suggested) {
+            if (!present.contains(s.getPath())) {
+                boolean add = true;
+                for (String exclusivePath : s.getExclusivePaths()) {
+                    if (present.contains(exclusivePath)) {
+                        add = false;
+                        continue;
+                    }
+                }
+                if (add) {
+                    items.add(s);
+                }
+            }
+        }
+        if (assetsLoaded) {
+            storeAssets();
+        }
+        changeSupport.fireChange();
+    }
+
+    public List<OCIItem> getItems() {
+        List<OCIItem> list = new ArrayList<>(items);
+        Collections.sort(list, (a, b) -> {
+            if ("Suggested".equals(a.getKey().getPath())) {

Review Comment:
   I'd make a constant for `Suggested` string 
   
   Just an idea: since the "suggested" items are NOT serialized, and only 
sorted at the end of this list - wouldn't it be better to have them separate 
List field, and only join them for this `getItem` ?



##########
enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/CloudAssets.java:
##########
@@ -0,0 +1,279 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.cloud.oracle.assets;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonIOException;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonParser;
+import com.google.gson.JsonSyntaxException;
+import com.google.gson.stream.JsonReader;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.StringReader;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.swing.event.ChangeListener;
+import org.netbeans.api.db.explorer.ConnectionListener;
+import org.netbeans.api.db.explorer.ConnectionManager;
+import org.netbeans.api.db.explorer.DatabaseConnection;
+import org.netbeans.modules.cloud.oracle.bucket.BucketItem;
+import org.netbeans.modules.cloud.oracle.compute.ClusterItem;
+import org.netbeans.modules.cloud.oracle.compute.ComputeInstanceItem;
+import org.netbeans.modules.cloud.oracle.database.DatabaseItem;
+import org.netbeans.modules.cloud.oracle.items.OCID;
+import org.netbeans.modules.cloud.oracle.items.OCIItem;
+import org.netbeans.modules.cloud.oracle.vault.VaultItem;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.modules.OnStart;
+import org.openide.util.ChangeSupport;
+import org.openide.util.Exceptions;
+
+/**
+ *
+ * @author Jan Horvath
+ */
+public class CloudAssets {
+
+    private static final String CLOUD_ASSETS_PATH = "CloudAssets"; //NOI18N
+    private static final String CLOUD_ASSETS_FILE = "default.json"; //NOI18N
+    private static CloudAssets instance = null;
+
+    private boolean assetsLoaded = false;
+    private Set<OCIItem> items = new HashSet<>();
+
+    private final ChangeSupport changeSupport;
+    private final Gson gson;
+
+    CloudAssets() {
+        this.gson = new GsonBuilder()
+                .setPrettyPrinting()
+                .registerTypeAdapter(OCID.class, new OCIDDeserializer())
+                .create();
+        changeSupport = new ChangeSupport(this);
+        ConnectionManager.getDefault().addConnectionListener(() -> {
+            DatabaseConnection[] connections = 
ConnectionManager.getDefault().getConnections();
+            Set<String> ocids = new HashSet<>();
+            for (int i = 0; i < connections.length; i++) {
+                ocids.add((String) 
connections[i].getConnectionProperties().get("OCID")); //NOI18N
+            }
+            boolean update = false;
+            for (Iterator it = items.iterator(); it.hasNext();) {
+                OCIItem item = (OCIItem) it.next();
+                if (!ocids.contains(item.getKey().getValue()) && 
"Databases".equals(item.getKey().getPath())) { //NOI18N
+                    it.remove();
+                    update = true;
+                }
+            }
+            if (update) {
+                update();
+            }
+        });
+    }
+
+    public static synchronized CloudAssets getDefault() {
+        if (instance == null) {
+            instance = new CloudAssets();
+        }
+        return instance;
+    }
+
+    @OnStart
+    public static final class Init implements Runnable {
+
+        @Override
+        public void run() {
+            CloudAssets.getDefault().loadAssets();
+        }
+
+    }
+
+    public void addItem(OCIItem newItem) {
+        items.add(newItem);
+        update();
+    }
+
+    void removeItem(OCIItem item) {
+        if (items.remove(item)) {
+            update();
+        }
+    }
+
+    public void update() {
+        OpenProjectsFinder.getDefault().findOpenProjects().thenAccept(projects 
-> {
+            SuggestionAnalyzer analyzer = new DependenciesAnalyzer();
+            Set<SuggestedItem> suggested = analyzer.findSuggestions(projects);
+            setSuggestions(suggested);
+        });
+    }
+
+    public synchronized void setSuggestions(Set<SuggestedItem> suggested) {
+        for (OCIItem item : new HashSet<OCIItem>(items)) {
+            if (item.getKey().getPath().equals("Suggested")) { //NOI18N
+                items.remove(item);
+            }
+        }
+        Set<String> present = items.stream()
+                .map(i -> i.getKey().getPath())
+                .collect(Collectors.toSet());
+        for (SuggestedItem s : suggested) {
+            if (!present.contains(s.getPath())) {
+                boolean add = true;
+                for (String exclusivePath : s.getExclusivePaths()) {
+                    if (present.contains(exclusivePath)) {
+                        add = false;
+                        continue;
+                    }
+                }
+                if (add) {
+                    items.add(s);
+                }
+            }
+        }
+        if (assetsLoaded) {
+            storeAssets();

Review Comment:
   maybe store assets & fire the change only if a change was actually made to 
items (removed, added).



##########
enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/CreatePoliciesCommand.java:
##########
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.cloud.oracle.assets;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.logging.Logger;
+import org.netbeans.modules.cloud.oracle.policy.PolicyGenerator;
+import org.netbeans.spi.lsp.CommandProvider;
+import org.openide.DialogDisplayer;
+import org.openide.NotifyDescriptor;
+import org.openide.util.Exceptions;
+import org.openide.util.RequestProcessor;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author Jan Horvath
+ */
+@ServiceProvider(service = CommandProvider.class)
+public class CreatePoliciesCommand implements CommandProvider {
+    private static final Logger LOG = 
Logger.getLogger(CreatePoliciesCommand.class.getName());
+    
+    
+
+    private static final String COMMAND_CREATE_POLICIES = 
"nbls.cloud.assets.policy.create.local"; //NOI18N
+    private static final String COMMAND_CREATE_CONFIG = 
"nbls.cloud.assets.config.create.local"; //NOI18N
+    private static final String COMMAND_CLOUD_ASSETS_REFRESH = 
"nbls.cloud.assets.refresh"; //NOI18N
+
+    private static final Set COMMANDS = new HashSet<>(Arrays.asList(
+            COMMAND_CREATE_POLICIES,
+            COMMAND_CREATE_CONFIG,
+            COMMAND_CLOUD_ASSETS_REFRESH
+    ));
+
+    @Override
+    public Set<String> getCommands() {
+        return Collections.unmodifiableSet(COMMANDS);
+    }
+
+    @Override
+    public CompletableFuture<Object> runCommand(String command, List<Object> 
arguments) {
+        CompletableFuture future = new CompletableFuture();
+        if (COMMAND_CREATE_POLICIES.equals(command)) {
+                RequestProcessor.getDefault().post(() -> {

Review Comment:
   Please create a dedicated RP for this.



##########
enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/PropertiesGenerator.java:
##########
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.cloud.oracle.assets;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.netbeans.api.db.explorer.ConnectionManager;
+import org.netbeans.api.db.explorer.DatabaseConnection;
+import org.netbeans.modules.cloud.oracle.bucket.BucketItem;
+import org.netbeans.modules.cloud.oracle.items.OCIItem;
+
+/**
+ *
+ * @author Jan Horvath
+ */
+public class PropertiesGenerator {
+
+    private static PropertiesGenerator instance = null;
+
+    private PropertiesGenerator() {
+    }
+
+    public static synchronized PropertiesGenerator getDefault() {

Review Comment:
   properties generator holds no state -- it can be just an utility class with 
static methods, not a singleton ?



##########
java/java.lsp.server/vscode/package.json:
##########
@@ -4,7 +4,7 @@
        "description": "Apache NetBeans Language Server Extension for Visual 
Studio Code",
        "author": "Apache NetBeans",
        "license": "Apache 2.0",
-       "version": "0.1.0",
+       "version": "99.8.0",

Review Comment:
   Intentional change ?



##########
enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/SuggestedItem.java:
##########
@@ -28,14 +30,36 @@
 public final class SuggestedItem extends OCIItem {
 
     private final String path;
+    private final Set<String> exclusivePaths;
 
-    public SuggestedItem(String path, String name) {
+    public SuggestedItem(String path, String name, Set<String> exclusivePaths) 
{
         super(OCID.of("", "Suggested"), null, name); //NOI18N
         this.path = path;
+        this.exclusivePaths = exclusivePaths;
     }
 
     public String getPath() {
         return path;
     }
-    
+
+    public Set<String> getExclusivePaths() {
+        return exclusivePaths;
+    }
+
+    public static SuggestedItem forPath(String path) {
+        switch (path) {
+            case "Databases":
+                return new SuggestedItem("Databases", "Select Oracle 
Autonomous Database", Collections.emptySet());

Review Comment:
   Bundle / I18N for labels.



##########
enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/CreatePoliciesCommand.java:
##########
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.cloud.oracle.assets;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.logging.Logger;
+import org.netbeans.modules.cloud.oracle.policy.PolicyGenerator;
+import org.netbeans.spi.lsp.CommandProvider;
+import org.openide.DialogDisplayer;
+import org.openide.NotifyDescriptor;
+import org.openide.util.Exceptions;
+import org.openide.util.RequestProcessor;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author Jan Horvath
+ */
+@ServiceProvider(service = CommandProvider.class)
+public class CreatePoliciesCommand implements CommandProvider {
+    private static final Logger LOG = 
Logger.getLogger(CreatePoliciesCommand.class.getName());
+    
+    
+
+    private static final String COMMAND_CREATE_POLICIES = 
"nbls.cloud.assets.policy.create.local"; //NOI18N
+    private static final String COMMAND_CREATE_CONFIG = 
"nbls.cloud.assets.config.create.local"; //NOI18N
+    private static final String COMMAND_CLOUD_ASSETS_REFRESH = 
"nbls.cloud.assets.refresh"; //NOI18N
+
+    private static final Set COMMANDS = new HashSet<>(Arrays.asList(
+            COMMAND_CREATE_POLICIES,
+            COMMAND_CREATE_CONFIG,
+            COMMAND_CLOUD_ASSETS_REFRESH
+    ));
+
+    @Override
+    public Set<String> getCommands() {
+        return Collections.unmodifiableSet(COMMANDS);
+    }
+
+    @Override
+    public CompletableFuture<Object> runCommand(String command, List<Object> 
arguments) {
+        CompletableFuture future = new CompletableFuture();
+        if (COMMAND_CREATE_POLICIES.equals(command)) {
+                RequestProcessor.getDefault().post(() -> {
+                    try {
+                        String policies = 
PolicyGenerator.createPolicies(CloudAssets.getDefault().getItems());
+                        future.complete(policies);
+                    } catch (IllegalStateException e) {
+                        NotifyDescriptor.Message msg = new 
NotifyDescriptor.Message(e.getMessage());
+                        DialogDisplayer.getDefault().notify(msg);

Review Comment:
   use `notifyLater` or better `notifyFuture` to avoid blocking the RP.



##########
enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/CloudAssets.java:
##########
@@ -0,0 +1,279 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.cloud.oracle.assets;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonIOException;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonParser;
+import com.google.gson.JsonSyntaxException;
+import com.google.gson.stream.JsonReader;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.StringReader;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.swing.event.ChangeListener;
+import org.netbeans.api.db.explorer.ConnectionListener;
+import org.netbeans.api.db.explorer.ConnectionManager;
+import org.netbeans.api.db.explorer.DatabaseConnection;
+import org.netbeans.modules.cloud.oracle.bucket.BucketItem;
+import org.netbeans.modules.cloud.oracle.compute.ClusterItem;
+import org.netbeans.modules.cloud.oracle.compute.ComputeInstanceItem;
+import org.netbeans.modules.cloud.oracle.database.DatabaseItem;
+import org.netbeans.modules.cloud.oracle.items.OCID;
+import org.netbeans.modules.cloud.oracle.items.OCIItem;
+import org.netbeans.modules.cloud.oracle.vault.VaultItem;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.modules.OnStart;
+import org.openide.util.ChangeSupport;
+import org.openide.util.Exceptions;
+
+/**
+ *
+ * @author Jan Horvath
+ */
+public class CloudAssets {
+
+    private static final String CLOUD_ASSETS_PATH = "CloudAssets"; //NOI18N
+    private static final String CLOUD_ASSETS_FILE = "default.json"; //NOI18N
+    private static CloudAssets instance = null;
+
+    private boolean assetsLoaded = false;
+    private Set<OCIItem> items = new HashSet<>();
+
+    private final ChangeSupport changeSupport;
+    private final Gson gson;
+
+    CloudAssets() {
+        this.gson = new GsonBuilder()
+                .setPrettyPrinting()
+                .registerTypeAdapter(OCID.class, new OCIDDeserializer())
+                .create();
+        changeSupport = new ChangeSupport(this);
+        ConnectionManager.getDefault().addConnectionListener(() -> {
+            DatabaseConnection[] connections = 
ConnectionManager.getDefault().getConnections();
+            Set<String> ocids = new HashSet<>();
+            for (int i = 0; i < connections.length; i++) {
+                ocids.add((String) 
connections[i].getConnectionProperties().get("OCID")); //NOI18N

Review Comment:
   Nitpick: `null`s will appear in `ocids` for non-OCI connections. Shouldn't 
harm the .contains() test.



##########
enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/CloudAssets.java:
##########
@@ -0,0 +1,279 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.cloud.oracle.assets;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonIOException;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonParser;
+import com.google.gson.JsonSyntaxException;
+import com.google.gson.stream.JsonReader;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.StringReader;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.swing.event.ChangeListener;
+import org.netbeans.api.db.explorer.ConnectionListener;
+import org.netbeans.api.db.explorer.ConnectionManager;
+import org.netbeans.api.db.explorer.DatabaseConnection;
+import org.netbeans.modules.cloud.oracle.bucket.BucketItem;
+import org.netbeans.modules.cloud.oracle.compute.ClusterItem;
+import org.netbeans.modules.cloud.oracle.compute.ComputeInstanceItem;
+import org.netbeans.modules.cloud.oracle.database.DatabaseItem;
+import org.netbeans.modules.cloud.oracle.items.OCID;
+import org.netbeans.modules.cloud.oracle.items.OCIItem;
+import org.netbeans.modules.cloud.oracle.vault.VaultItem;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.modules.OnStart;
+import org.openide.util.ChangeSupport;
+import org.openide.util.Exceptions;
+
+/**
+ *
+ * @author Jan Horvath
+ */
+public class CloudAssets {
+
+    private static final String CLOUD_ASSETS_PATH = "CloudAssets"; //NOI18N
+    private static final String CLOUD_ASSETS_FILE = "default.json"; //NOI18N
+    private static CloudAssets instance = null;
+
+    private boolean assetsLoaded = false;
+    private Set<OCIItem> items = new HashSet<>();
+
+    private final ChangeSupport changeSupport;
+    private final Gson gson;
+
+    CloudAssets() {
+        this.gson = new GsonBuilder()
+                .setPrettyPrinting()
+                .registerTypeAdapter(OCID.class, new OCIDDeserializer())
+                .create();
+        changeSupport = new ChangeSupport(this);
+        ConnectionManager.getDefault().addConnectionListener(() -> {
+            DatabaseConnection[] connections = 
ConnectionManager.getDefault().getConnections();
+            Set<String> ocids = new HashSet<>();
+            for (int i = 0; i < connections.length; i++) {
+                ocids.add((String) 
connections[i].getConnectionProperties().get("OCID")); //NOI18N
+            }
+            boolean update = false;
+            for (Iterator it = items.iterator(); it.hasNext();) {
+                OCIItem item = (OCIItem) it.next();
+                if (!ocids.contains(item.getKey().getValue()) && 
"Databases".equals(item.getKey().getPath())) { //NOI18N
+                    it.remove();
+                    update = true;
+                }
+            }
+            if (update) {
+                update();
+            }
+        });
+    }
+
+    public static synchronized CloudAssets getDefault() {
+        if (instance == null) {
+            instance = new CloudAssets();
+        }
+        return instance;
+    }
+
+    @OnStart
+    public static final class Init implements Runnable {

Review Comment:
   Is it necessary to loadAssets() on the start -- since we're tracking 
`assetsLoaded`, items could be lazy-initialized before they're referenced ?



##########
enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/actions/AddADBAction.java:
##########
@@ -100,6 +100,10 @@ public class AddADBAction implements ActionListener {
     })
     @Override
     public void actionPerformed(ActionEvent e) {
+        addADB(e);
+    }
+    
+    public DatabaseItem addADB(ActionEvent e) {

Review Comment:
   Is `ActionEvent` parameter ever used in the method ?



##########
enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/OCITenancyProvider.java:
##########
@@ -127,7 +127,7 @@ private void refresh() {
         }
         List<ProfileKey> currentKeys = new ArrayList<>();
         for (OCIProfile p : OCIManager.getDefault().getConnectedProfiles()) {
-            ProfileKey k = new ProfileKey(p.getConfigPath(), p.getId(), 
p.getTenantId());
+            ProfileKey k = new ProfileKey(p.getConfigPath(), p.getId(), 
p.getTenancy().get().getKey().getValue());

Review Comment:
   Is it possible that the execution reach here for a profile with unitialized 
`configProvider` ? It would error out here (previously it passed `null` to 
profile key).



##########
ide/project.dependency/src/org/netbeans/modules/project/dependency/ProjectDependencies.java:
##########
@@ -68,7 +69,14 @@ public static DependencyQueryBuilder newBuilder() {
      * @return the query instance
      */
     public static DependencyQuery newQuery(Scope... scopes) {
-        return newBuilder().scope(scopes).build();
+        DependencyQuery x = null;

Review Comment:
   Probably a leftover ?



##########
enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/CreatePoliciesCommand.java:
##########
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.cloud.oracle.assets;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.logging.Logger;
+import org.netbeans.modules.cloud.oracle.policy.PolicyGenerator;
+import org.netbeans.spi.lsp.CommandProvider;
+import org.openide.DialogDisplayer;
+import org.openide.NotifyDescriptor;
+import org.openide.util.Exceptions;
+import org.openide.util.RequestProcessor;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author Jan Horvath
+ */
+@ServiceProvider(service = CommandProvider.class)
+public class CreatePoliciesCommand implements CommandProvider {
+    private static final Logger LOG = 
Logger.getLogger(CreatePoliciesCommand.class.getName());
+    
+    
+
+    private static final String COMMAND_CREATE_POLICIES = 
"nbls.cloud.assets.policy.create.local"; //NOI18N
+    private static final String COMMAND_CREATE_CONFIG = 
"nbls.cloud.assets.config.create.local"; //NOI18N
+    private static final String COMMAND_CLOUD_ASSETS_REFRESH = 
"nbls.cloud.assets.refresh"; //NOI18N
+
+    private static final Set COMMANDS = new HashSet<>(Arrays.asList(
+            COMMAND_CREATE_POLICIES,
+            COMMAND_CREATE_CONFIG,
+            COMMAND_CLOUD_ASSETS_REFRESH
+    ));
+
+    @Override
+    public Set<String> getCommands() {
+        return Collections.unmodifiableSet(COMMANDS);
+    }
+
+    @Override
+    public CompletableFuture<Object> runCommand(String command, List<Object> 
arguments) {
+        CompletableFuture future = new CompletableFuture();
+        if (COMMAND_CREATE_POLICIES.equals(command)) {
+                RequestProcessor.getDefault().post(() -> {
+                    try {
+                        String policies = 
PolicyGenerator.createPolicies(CloudAssets.getDefault().getItems());
+                        future.complete(policies);
+                    } catch (IllegalStateException e) {
+                        NotifyDescriptor.Message msg = new 
NotifyDescriptor.Message(e.getMessage());
+                        DialogDisplayer.getDefault().notify(msg);
+                        future.completeExceptionally(e);
+                    }
+                });
+            return future;
+        } else if (COMMAND_CREATE_CONFIG.equals(command)) {
+            StringWriter writer = new StringWriter();
+            Properties dbProps = new Properties();
+            
dbProps.putAll(PropertiesGenerator.getDefault().getProperties(null));
+            try {
+                dbProps.store(writer, "Generated application.properties\n" 
//NOI18N
+                    + "Uncomment following line when running inside Oracle 
Cloud\n" //NOI18N
+                    + "oci.config.instance-principal.enabled=true"); //NOI18N
+                future.complete(writer.toString());
+            } catch (IOException ex) {
+                future.complete(null);
+                Exceptions.printStackTrace(ex);

Review Comment:
   why not future.completeExceptionally ?



##########
enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/CloudAssets.java:
##########
@@ -0,0 +1,279 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.cloud.oracle.assets;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonIOException;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonParser;
+import com.google.gson.JsonSyntaxException;
+import com.google.gson.stream.JsonReader;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.StringReader;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.swing.event.ChangeListener;
+import org.netbeans.api.db.explorer.ConnectionListener;
+import org.netbeans.api.db.explorer.ConnectionManager;
+import org.netbeans.api.db.explorer.DatabaseConnection;
+import org.netbeans.modules.cloud.oracle.bucket.BucketItem;
+import org.netbeans.modules.cloud.oracle.compute.ClusterItem;
+import org.netbeans.modules.cloud.oracle.compute.ComputeInstanceItem;
+import org.netbeans.modules.cloud.oracle.database.DatabaseItem;
+import org.netbeans.modules.cloud.oracle.items.OCID;
+import org.netbeans.modules.cloud.oracle.items.OCIItem;
+import org.netbeans.modules.cloud.oracle.vault.VaultItem;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.modules.OnStart;
+import org.openide.util.ChangeSupport;
+import org.openide.util.Exceptions;
+
+/**
+ *
+ * @author Jan Horvath
+ */
+public class CloudAssets {
+
+    private static final String CLOUD_ASSETS_PATH = "CloudAssets"; //NOI18N
+    private static final String CLOUD_ASSETS_FILE = "default.json"; //NOI18N
+    private static CloudAssets instance = null;
+
+    private boolean assetsLoaded = false;
+    private Set<OCIItem> items = new HashSet<>();
+
+    private final ChangeSupport changeSupport;
+    private final Gson gson;
+
+    CloudAssets() {
+        this.gson = new GsonBuilder()
+                .setPrettyPrinting()
+                .registerTypeAdapter(OCID.class, new OCIDDeserializer())
+                .create();
+        changeSupport = new ChangeSupport(this);
+        ConnectionManager.getDefault().addConnectionListener(() -> {
+            DatabaseConnection[] connections = 
ConnectionManager.getDefault().getConnections();
+            Set<String> ocids = new HashSet<>();
+            for (int i = 0; i < connections.length; i++) {
+                ocids.add((String) 
connections[i].getConnectionProperties().get("OCID")); //NOI18N
+            }
+            boolean update = false;
+            for (Iterator it = items.iterator(); it.hasNext();) {
+                OCIItem item = (OCIItem) it.next();
+                if (!ocids.contains(item.getKey().getValue()) && 
"Databases".equals(item.getKey().getPath())) { //NOI18N
+                    it.remove();
+                    update = true;
+                }
+            }
+            if (update) {
+                update();
+            }
+        });
+    }
+
+    public static synchronized CloudAssets getDefault() {
+        if (instance == null) {
+            instance = new CloudAssets();
+        }
+        return instance;
+    }
+
+    @OnStart
+    public static final class Init implements Runnable {
+
+        @Override
+        public void run() {
+            CloudAssets.getDefault().loadAssets();
+        }
+
+    }
+
+    public void addItem(OCIItem newItem) {
+        items.add(newItem);
+        update();
+    }
+
+    void removeItem(OCIItem item) {
+        if (items.remove(item)) {
+            update();
+        }
+    }
+
+    public void update() {
+        OpenProjectsFinder.getDefault().findOpenProjects().thenAccept(projects 
-> {
+            SuggestionAnalyzer analyzer = new DependenciesAnalyzer();
+            Set<SuggestedItem> suggested = analyzer.findSuggestions(projects);
+            setSuggestions(suggested);
+        });
+    }
+
+    public synchronized void setSuggestions(Set<SuggestedItem> suggested) {
+        for (OCIItem item : new HashSet<OCIItem>(items)) {
+            if (item.getKey().getPath().equals("Suggested")) { //NOI18N
+                items.remove(item);
+            }
+        }
+        Set<String> present = items.stream()
+                .map(i -> i.getKey().getPath())
+                .collect(Collectors.toSet());
+        for (SuggestedItem s : suggested) {
+            if (!present.contains(s.getPath())) {
+                boolean add = true;
+                for (String exclusivePath : s.getExclusivePaths()) {
+                    if (present.contains(exclusivePath)) {
+                        add = false;
+                        continue;
+                    }
+                }
+                if (add) {
+                    items.add(s);
+                }
+            }
+        }
+        if (assetsLoaded) {
+            storeAssets();
+        }
+        changeSupport.fireChange();
+    }
+
+    public List<OCIItem> getItems() {
+        List<OCIItem> list = new ArrayList<>(items);
+        Collections.sort(list, (a, b) -> {
+            if ("Suggested".equals(a.getKey().getPath())) {
+                return Integer.MIN_VALUE;
+            }
+            if ("Suggested".equals(b.getKey().getPath())) {
+                return Integer.MAX_VALUE;
+            }
+            return a.getKey().getPath().compareTo(b.getKey().getPath());
+        });
+        return list;
+    }
+
+    /**
+     * Adds a <code>ChangeListener</code> to the listener list.
+     *
+     * @param listener the <code>ChangeListener</code> to be added.
+     */
+    public void addChangeListener(ChangeListener listener) {
+        changeSupport.addChangeListener(listener);
+    }
+
+    /**
+     * Removes a <code>ChangeListener</code> from the listener list.
+     *
+     * @param listener the <code>ChangeListener</code> to be removed.
+     */
+    public void removeChangeListener(ChangeListener listener) {
+        changeSupport.removeChangeListener(listener);
+    }
+
+    synchronized void storeAssets() {
+        Set<OCIItem> toStore = items.stream()
+                .filter(i -> !"Suggested".equals(i.getKey().getPath()))
+                .collect(Collectors.toSet());
+        try {
+            FileObject fo = FileUtil.createFolder(FileUtil.getConfigRoot(), 
CLOUD_ASSETS_PATH);
+            FileObject file = fo.getFileObject(CLOUD_ASSETS_FILE);
+            if (file == null) {
+                file = fo.createData(CLOUD_ASSETS_FILE);
+            }
+            if (file != null) {
+                OutputStream os = file.getOutputStream();
+                os.write(gson.toJson(toStore).getBytes());
+                os.close();
+            }
+        } catch (IOException ex) {
+            Exceptions.printStackTrace(ex);
+        }
+    }
+
+    synchronized void loadAssets() {
+        JsonReader reader = null;
+        try {
+            FileObject fo = FileUtil.createFolder(FileUtil.getConfigRoot(), 
CLOUD_ASSETS_PATH);
+            FileObject file = fo.getFileObject(CLOUD_ASSETS_FILE);
+            String content = new String(file.asBytes());
+
+            reader = new JsonReader(new StringReader(content));

Review Comment:
   use try-with-resources for the reader, it will close it.



##########
enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/DependenciesAnalyzer.java:
##########
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.cloud.oracle.assets;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.netbeans.api.project.Project;
+import org.netbeans.modules.project.dependency.Dependency;
+import org.netbeans.modules.project.dependency.DependencyResult;
+import org.netbeans.modules.project.dependency.ProjectDependencies;
+
+/**
+ *
+ * @author Jan Horvath
+ */
+public final class DependenciesAnalyzer implements SuggestionAnalyzer {
+
+    private static final Map<String, String> DEPENDENCIES = new HashMap() {
+        {
+            put("micronaut-oraclecloud-atp", "Databases");
+            put("micronaut-object-storage-oracle-cloud", "Bucket");
+            put("micronaut-oraclecloud-vault", "Vault");
+        }
+    };
+
+    @Override
+    public Set<SuggestedItem> findSuggestions(Project[] projects) {
+        Set<SuggestedItem> result = new HashSet<>();
+        if (projects == null) {
+            return null;
+        }
+        for (int i = 0; i < projects.length; i++) {
+            ProjectDependencies.DependencyQueryBuilder b = 
ProjectDependencies.newBuilder().online();
+            DependencyResult r;
+            try {
+                r = ProjectDependencies.findDependencies(projects[i], 
b.build());
+            } catch (ThreadDeath td) {

Review Comment:
   A leftover ? 



##########
enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/OpenProjectsFinder.java:
##########
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.cloud.oracle.assets;
+
+import java.util.concurrent.CompletableFuture;
+import org.netbeans.api.project.Project;
+import org.openide.util.Lookup;
+
+/**
+ *
+ * @author Jan Horvath
+ */
+public abstract class OpenProjectsFinder {

Review Comment:
   We may need to make this a regular API somehow (in the future) ... could you 
pls. specify the semantic / differences between this and (regular) 
OpenProjects.xxx methods ?



##########
enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/CloudAssets.java:
##########
@@ -0,0 +1,279 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.cloud.oracle.assets;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonIOException;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonParser;
+import com.google.gson.JsonSyntaxException;
+import com.google.gson.stream.JsonReader;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.StringReader;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.swing.event.ChangeListener;
+import org.netbeans.api.db.explorer.ConnectionListener;
+import org.netbeans.api.db.explorer.ConnectionManager;
+import org.netbeans.api.db.explorer.DatabaseConnection;
+import org.netbeans.modules.cloud.oracle.bucket.BucketItem;
+import org.netbeans.modules.cloud.oracle.compute.ClusterItem;
+import org.netbeans.modules.cloud.oracle.compute.ComputeInstanceItem;
+import org.netbeans.modules.cloud.oracle.database.DatabaseItem;
+import org.netbeans.modules.cloud.oracle.items.OCID;
+import org.netbeans.modules.cloud.oracle.items.OCIItem;
+import org.netbeans.modules.cloud.oracle.vault.VaultItem;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.modules.OnStart;
+import org.openide.util.ChangeSupport;
+import org.openide.util.Exceptions;
+
+/**
+ *
+ * @author Jan Horvath
+ */
+public class CloudAssets {
+
+    private static final String CLOUD_ASSETS_PATH = "CloudAssets"; //NOI18N
+    private static final String CLOUD_ASSETS_FILE = "default.json"; //NOI18N
+    private static CloudAssets instance = null;
+
+    private boolean assetsLoaded = false;
+    private Set<OCIItem> items = new HashSet<>();
+
+    private final ChangeSupport changeSupport;
+    private final Gson gson;
+
+    CloudAssets() {
+        this.gson = new GsonBuilder()
+                .setPrettyPrinting()
+                .registerTypeAdapter(OCID.class, new OCIDDeserializer())
+                .create();
+        changeSupport = new ChangeSupport(this);
+        ConnectionManager.getDefault().addConnectionListener(() -> {
+            DatabaseConnection[] connections = 
ConnectionManager.getDefault().getConnections();
+            Set<String> ocids = new HashSet<>();
+            for (int i = 0; i < connections.length; i++) {
+                ocids.add((String) 
connections[i].getConnectionProperties().get("OCID")); //NOI18N
+            }
+            boolean update = false;
+            for (Iterator it = items.iterator(); it.hasNext();) {
+                OCIItem item = (OCIItem) it.next();
+                if (!ocids.contains(item.getKey().getValue()) && 
"Databases".equals(item.getKey().getPath())) { //NOI18N
+                    it.remove();
+                    update = true;
+                }
+            }
+            if (update) {
+                update();
+            }
+        });
+    }
+
+    public static synchronized CloudAssets getDefault() {
+        if (instance == null) {
+            instance = new CloudAssets();
+        }
+        return instance;
+    }
+
+    @OnStart
+    public static final class Init implements Runnable {
+
+        @Override
+        public void run() {
+            CloudAssets.getDefault().loadAssets();
+        }
+
+    }
+
+    public void addItem(OCIItem newItem) {
+        items.add(newItem);
+        update();
+    }
+
+    void removeItem(OCIItem item) {
+        if (items.remove(item)) {
+            update();
+        }
+    }
+
+    public void update() {
+        OpenProjectsFinder.getDefault().findOpenProjects().thenAccept(projects 
-> {
+            SuggestionAnalyzer analyzer = new DependenciesAnalyzer();
+            Set<SuggestedItem> suggested = analyzer.findSuggestions(projects);
+            setSuggestions(suggested);
+        });
+    }
+
+    public synchronized void setSuggestions(Set<SuggestedItem> suggested) {
+        for (OCIItem item : new HashSet<OCIItem>(items)) {
+            if (item.getKey().getPath().equals("Suggested")) { //NOI18N
+                items.remove(item);
+            }
+        }
+        Set<String> present = items.stream()
+                .map(i -> i.getKey().getPath())
+                .collect(Collectors.toSet());
+        for (SuggestedItem s : suggested) {
+            if (!present.contains(s.getPath())) {
+                boolean add = true;
+                for (String exclusivePath : s.getExclusivePaths()) {
+                    if (present.contains(exclusivePath)) {
+                        add = false;
+                        continue;
+                    }
+                }
+                if (add) {
+                    items.add(s);
+                }
+            }
+        }
+        if (assetsLoaded) {
+            storeAssets();
+        }
+        changeSupport.fireChange();
+    }
+
+    public List<OCIItem> getItems() {
+        List<OCIItem> list = new ArrayList<>(items);
+        Collections.sort(list, (a, b) -> {
+            if ("Suggested".equals(a.getKey().getPath())) {
+                return Integer.MIN_VALUE;
+            }
+            if ("Suggested".equals(b.getKey().getPath())) {
+                return Integer.MAX_VALUE;
+            }
+            return a.getKey().getPath().compareTo(b.getKey().getPath());
+        });
+        return list;
+    }
+
+    /**
+     * Adds a <code>ChangeListener</code> to the listener list.
+     *
+     * @param listener the <code>ChangeListener</code> to be added.
+     */
+    public void addChangeListener(ChangeListener listener) {
+        changeSupport.addChangeListener(listener);
+    }
+
+    /**
+     * Removes a <code>ChangeListener</code> from the listener list.
+     *
+     * @param listener the <code>ChangeListener</code> to be removed.
+     */
+    public void removeChangeListener(ChangeListener listener) {
+        changeSupport.removeChangeListener(listener);
+    }
+
+    synchronized void storeAssets() {
+        Set<OCIItem> toStore = items.stream()
+                .filter(i -> !"Suggested".equals(i.getKey().getPath()))
+                .collect(Collectors.toSet());
+        try {
+            FileObject fo = FileUtil.createFolder(FileUtil.getConfigRoot(), 
CLOUD_ASSETS_PATH);
+            FileObject file = fo.getFileObject(CLOUD_ASSETS_FILE);
+            if (file == null) {
+                file = fo.createData(CLOUD_ASSETS_FILE);
+            }
+            if (file != null) {
+                OutputStream os = file.getOutputStream();

Review Comment:
   use try -with-resources. 
   Maybe use FileLock, so eventual NB reads are postponed after the write+close 
completes
   



##########
enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/Steps.java:
##########
@@ -0,0 +1,656 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.cloud.oracle.assets;
+
+import com.oracle.bmc.identity.Identity;
+import com.oracle.bmc.identity.IdentityClient;
+import com.oracle.bmc.identity.model.Compartment;
+import com.oracle.bmc.identity.model.Tenancy;
+import com.oracle.bmc.identity.requests.ListCompartmentsRequest;
+import com.oracle.bmc.identity.responses.ListCompartmentsResponse;
+import com.oracle.bmc.model.BmcException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.TreeMap;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import org.netbeans.api.progress.ProgressHandle;
+import org.netbeans.api.project.Project;
+import org.netbeans.modules.cloud.oracle.OCIManager;
+import org.netbeans.modules.cloud.oracle.OCIProfile;
+import org.netbeans.modules.cloud.oracle.OCISessionInitiator;
+import org.netbeans.modules.cloud.oracle.bucket.BucketNode;
+import org.netbeans.modules.cloud.oracle.compartment.CompartmentItem;
+import org.netbeans.modules.cloud.oracle.compute.ClusterNode;
+import org.netbeans.modules.cloud.oracle.compute.ComputeInstanceNode;
+import org.netbeans.modules.cloud.oracle.database.DatabaseItem;
+import org.netbeans.modules.cloud.oracle.database.DatabaseNode;
+import org.netbeans.modules.cloud.oracle.items.OCID;
+import org.netbeans.modules.cloud.oracle.items.OCIItem;
+import org.netbeans.modules.cloud.oracle.items.TenancyItem;
+import org.netbeans.modules.cloud.oracle.vault.VaultNode;
+import org.openide.DialogDescriptor;
+import org.openide.DialogDisplayer;
+import org.openide.NotifyDescriptor;
+import org.openide.util.Exceptions;
+import org.openide.util.Lookup;
+import org.openide.util.Pair;
+import org.openide.util.RequestProcessor;
+
+/**
+ *
+ * @author Jan Horvath
+ */
+public final class Steps {
+    private static final Logger LOG = Logger.getLogger(Steps.class.getName());
+    private static Steps instance = null;
+
+    public static synchronized Steps getDefault() {
+        if (instance == null) {
+            instance = new Steps();
+        }
+        return instance;
+    }
+
+    public CompletableFuture<Object> executeMultistep(Step firstStep, Lookup 
lookup) {
+        DialogDisplayer dd = DialogDisplayer.getDefault();
+        CompletableFuture future = new CompletableFuture();
+        RequestProcessor.getDefault().post(() -> {

Review Comment:
   Do not use default request processor to wait on dialogs.



##########
enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/RootNode.java:
##########
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.cloud.oracle.assets;
+
+import java.util.List;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import org.netbeans.modules.cloud.oracle.NodeProvider;
+import org.netbeans.modules.cloud.oracle.OCIManager;
+import org.netbeans.modules.cloud.oracle.OCISessionInitiator;
+import org.netbeans.modules.cloud.oracle.items.OCIItem;
+import org.openide.nodes.AbstractNode;
+import org.openide.nodes.ChildFactory;
+import org.openide.nodes.Children;
+import org.openide.nodes.Node;
+import org.openide.util.lookup.Lookups;
+
+/**
+ *
+ * @author Jan Horvath
+ */
+public class RootNode {
+
+    static Node instance() {
+        return new AbstractNode(
+                Children.create(new 
AssetsChildren(OCIManager.getDefault().getActiveSession()), true));
+    }
+    
+    static class AssetsChildren extends ChildFactory<OCIItem> implements 
ChangeListener {
+
+        OCISessionInitiator session;
+
+        public AssetsChildren(OCISessionInitiator session) {
+            this.session = session;
+            CloudAssets.getDefault().addChangeListener(this);
+
+        }
+
+        @Override
+        protected boolean createKeys(List<OCIItem> toPopulate) {
+            toPopulate.addAll(CloudAssets.getDefault().getItems());
+            return true;
+        }
+
+        @Override
+        protected Node[] createNodesForKey(OCIItem key) {
+            NodeProvider nodeProvider = Lookups.forPath(
+                    String.format("Cloud/Oracle/%s/Nodes", 
key.getKey().getPath()))
+                    .lookup(NodeProvider.class);
+            if (nodeProvider instanceof NodeProvider.SessionAware) {
+                return new Node[]{((NodeProvider.SessionAware) 
nodeProvider).apply(key, session)};
+            } else {
+                return OCIManager.usingSession(session, ()

Review Comment:
   Maybe this could be moved to `default` method of the `NodeProvider` 
interface itself, so the base iface would delegate to apply() with 
usingSession() ?



##########
enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/DependenciesAnalyzer.java:
##########
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.cloud.oracle.assets;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.netbeans.api.project.Project;
+import org.netbeans.modules.project.dependency.Dependency;
+import org.netbeans.modules.project.dependency.DependencyResult;
+import org.netbeans.modules.project.dependency.ProjectDependencies;
+
+/**
+ *
+ * @author Jan Horvath
+ */
+public final class DependenciesAnalyzer implements SuggestionAnalyzer {
+
+    private static final Map<String, String> DEPENDENCIES = new HashMap() {
+        {
+            put("micronaut-oraclecloud-atp", "Databases");
+            put("micronaut-object-storage-oracle-cloud", "Bucket");
+            put("micronaut-oraclecloud-vault", "Vault");
+        }
+    };
+
+    @Override
+    public Set<SuggestedItem> findSuggestions(Project[] projects) {
+        Set<SuggestedItem> result = new HashSet<>();
+        if (projects == null) {
+            return null;
+        }
+        for (int i = 0; i < projects.length; i++) {
+            ProjectDependencies.DependencyQueryBuilder b = 
ProjectDependencies.newBuilder().online();
+            DependencyResult r;
+            try {
+                r = ProjectDependencies.findDependencies(projects[i], 
b.build());
+            } catch (ThreadDeath td) {
+                throw td;
+            }
+            List<Dependency> children = r.getRoot().getChildren();
+            for (Dependency dependency : children) {
+                String artifactId = dependency.getArtifact().getArtifactId();
+                if (DEPENDENCIES.containsKey(artifactId)) {

Review Comment:
   nitpick: I see that groupId is not checked, could be checked as well ?



##########
java/java.lsp.server/vscode/src/extension.ts:
##########
@@ -346,6 +346,22 @@ function shouldEnableConflictingJavaSupport() : boolean | 
undefined {
 }
 
 export function activate(context: ExtensionContext): VSNetBeansAPI {
+    
context.subscriptions.push(vscode.commands.registerCommand('cloud.assets.policy.create',
 async function (viewItem) {
+            const content = await 
vscode.commands.executeCommand('nbls.cloud.assets.policy.create.local') as 
string;
+            const document = 
vscode.Uri.parse(`policies.txt?${encodeURIComponent(content)}`);

Review Comment:
   Can't find usage of this variable -- please advise.



##########
enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/Steps.java:
##########
@@ -0,0 +1,656 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.cloud.oracle.assets;
+
+import com.oracle.bmc.identity.Identity;
+import com.oracle.bmc.identity.IdentityClient;
+import com.oracle.bmc.identity.model.Compartment;
+import com.oracle.bmc.identity.model.Tenancy;
+import com.oracle.bmc.identity.requests.ListCompartmentsRequest;
+import com.oracle.bmc.identity.responses.ListCompartmentsResponse;
+import com.oracle.bmc.model.BmcException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.TreeMap;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import org.netbeans.api.progress.ProgressHandle;
+import org.netbeans.api.project.Project;
+import org.netbeans.modules.cloud.oracle.OCIManager;
+import org.netbeans.modules.cloud.oracle.OCIProfile;
+import org.netbeans.modules.cloud.oracle.OCISessionInitiator;
+import org.netbeans.modules.cloud.oracle.bucket.BucketNode;
+import org.netbeans.modules.cloud.oracle.compartment.CompartmentItem;
+import org.netbeans.modules.cloud.oracle.compute.ClusterNode;
+import org.netbeans.modules.cloud.oracle.compute.ComputeInstanceNode;
+import org.netbeans.modules.cloud.oracle.database.DatabaseItem;
+import org.netbeans.modules.cloud.oracle.database.DatabaseNode;
+import org.netbeans.modules.cloud.oracle.items.OCID;
+import org.netbeans.modules.cloud.oracle.items.OCIItem;
+import org.netbeans.modules.cloud.oracle.items.TenancyItem;
+import org.netbeans.modules.cloud.oracle.vault.VaultNode;
+import org.openide.DialogDescriptor;
+import org.openide.DialogDisplayer;
+import org.openide.NotifyDescriptor;
+import org.openide.util.Exceptions;
+import org.openide.util.Lookup;
+import org.openide.util.Pair;
+import org.openide.util.RequestProcessor;
+
+/**
+ *
+ * @author Jan Horvath
+ */
+public final class Steps {
+    private static final Logger LOG = Logger.getLogger(Steps.class.getName());
+    private static Steps instance = null;
+
+    public static synchronized Steps getDefault() {
+        if (instance == null) {
+            instance = new Steps();
+        }
+        return instance;
+    }
+
+    public CompletableFuture<Object> executeMultistep(Step firstStep, Lookup 
lookup) {
+        DialogDisplayer dd = DialogDisplayer.getDefault();
+        CompletableFuture future = new CompletableFuture();
+        RequestProcessor.getDefault().post(() -> {
+            Multistep multistep = new Multistep(firstStep, lookup);
+            NotifyDescriptor.ComposedInput ci = new 
NotifyDescriptor.ComposedInput(Bundle.AddSuggestedItem(), 3, 
multistep.createInput());
+            if (DialogDescriptor.OK_OPTION == dd.notify(ci)) {
+                future.complete(multistep.getResult());
+            }

Review Comment:
   The future never completes if the dialog closes as not OK. It should somehow 
cancel the multi-step IMO.



##########
enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/Steps.java:
##########
@@ -0,0 +1,656 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.cloud.oracle.assets;
+
+import com.oracle.bmc.identity.Identity;
+import com.oracle.bmc.identity.IdentityClient;
+import com.oracle.bmc.identity.model.Compartment;
+import com.oracle.bmc.identity.model.Tenancy;
+import com.oracle.bmc.identity.requests.ListCompartmentsRequest;
+import com.oracle.bmc.identity.responses.ListCompartmentsResponse;
+import com.oracle.bmc.model.BmcException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.TreeMap;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import org.netbeans.api.progress.ProgressHandle;
+import org.netbeans.api.project.Project;
+import org.netbeans.modules.cloud.oracle.OCIManager;
+import org.netbeans.modules.cloud.oracle.OCIProfile;
+import org.netbeans.modules.cloud.oracle.OCISessionInitiator;
+import org.netbeans.modules.cloud.oracle.bucket.BucketNode;
+import org.netbeans.modules.cloud.oracle.compartment.CompartmentItem;
+import org.netbeans.modules.cloud.oracle.compute.ClusterNode;
+import org.netbeans.modules.cloud.oracle.compute.ComputeInstanceNode;
+import org.netbeans.modules.cloud.oracle.database.DatabaseItem;
+import org.netbeans.modules.cloud.oracle.database.DatabaseNode;
+import org.netbeans.modules.cloud.oracle.items.OCID;
+import org.netbeans.modules.cloud.oracle.items.OCIItem;
+import org.netbeans.modules.cloud.oracle.items.TenancyItem;
+import org.netbeans.modules.cloud.oracle.vault.VaultNode;
+import org.openide.DialogDescriptor;
+import org.openide.DialogDisplayer;
+import org.openide.NotifyDescriptor;
+import org.openide.util.Exceptions;
+import org.openide.util.Lookup;
+import org.openide.util.Pair;
+import org.openide.util.RequestProcessor;
+
+/**
+ *
+ * @author Jan Horvath
+ */
+public final class Steps {
+    private static final Logger LOG = Logger.getLogger(Steps.class.getName());
+    private static Steps instance = null;
+
+    public static synchronized Steps getDefault() {
+        if (instance == null) {
+            instance = new Steps();
+        }
+        return instance;
+    }
+
+    public CompletableFuture<Object> executeMultistep(Step firstStep, Lookup 
lookup) {
+        DialogDisplayer dd = DialogDisplayer.getDefault();
+        CompletableFuture future = new CompletableFuture();
+        RequestProcessor.getDefault().post(() -> {
+            Multistep multistep = new Multistep(firstStep, lookup);
+            NotifyDescriptor.ComposedInput ci = new 
NotifyDescriptor.ComposedInput(Bundle.AddSuggestedItem(), 3, 
multistep.createInput());
+            if (DialogDescriptor.OK_OPTION == dd.notify(ci)) {
+                future.complete(multistep.getResult());
+            }
+        });
+        return future;
+    }
+
+    private static class Multistep {
+
+        private final LinkedList<Step> steps = new LinkedList<>();
+        private final Lookup lookup;
+
+        Multistep(Step firstStep, Lookup lookup) {
+            steps.add(firstStep);
+            this.lookup = lookup;
+        }
+
+        NotifyDescriptor.ComposedInput.Callback createInput() {
+            return new NotifyDescriptor.ComposedInput.Callback() {
+                private int lastNumber = 0;
+
+                private void readValue(Step step, NotifyDescriptor desc) {
+                    String selected = null;
+                    if (!step.onlyOneChoice()) {
+                        if (desc instanceof NotifyDescriptor.QuickPick) {
+                            for (NotifyDescriptor.QuickPick.Item item : 
((NotifyDescriptor.QuickPick) desc).getItems()) {
+                                if (item.isSelected()) {
+                                    selected = item.getLabel();
+                                    break;
+                                }
+                            }
+                        } else if (desc instanceof NotifyDescriptor.InputLine) 
{
+                            selected = ((NotifyDescriptor.InputLine) 
desc).getInputText();
+                        }
+                        step.setValue(selected);
+                    }
+                }
+
+                @Override
+                public NotifyDescriptor 
createInput(NotifyDescriptor.ComposedInput input, int number) {
+                    if (number == 1) {
+                        while (steps.size() > 1) {
+                            steps.removeLast();
+                        }
+                        steps.getLast().prepare(null, lookup);
+                    } else if (lastNumber > number) {
+                        steps.removeLast();
+                        while (steps.getLast().onlyOneChoice() && steps.size() 
> 1) {

Review Comment:
   Shouldn't be .size() > lastNumber ?



##########
java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/db/DBConnectionProvider.java:
##########
@@ -207,4 +211,27 @@ private static void deleteTempFile(Path temp) {
             }
         }
     }
+    
+    private static PropertiesProvider getPropertiesProvider() {
+        if (propertiesProvider == null) {
+            propertiesProvider = 
Lookup.getDefault().lookup(PropertiesProvider.class);
+        }
+        if (propertiesProvider == null) {
+            propertiesProvider = new DefaultPropertiesProvider();
+        }
+        return propertiesProvider;
+    }
+    
+    public interface PropertiesProvider {

Review Comment:
   Please document, and make top-level class to be more visible as a contract 
to the integration module. Maybe name as `DatabaseDefaultProperties` ... or 
`OCIDatabaseDefaultProperties` ?



##########
enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/Steps.java:
##########
@@ -0,0 +1,656 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.cloud.oracle.assets;
+
+import com.oracle.bmc.identity.Identity;
+import com.oracle.bmc.identity.IdentityClient;
+import com.oracle.bmc.identity.model.Compartment;
+import com.oracle.bmc.identity.model.Tenancy;
+import com.oracle.bmc.identity.requests.ListCompartmentsRequest;
+import com.oracle.bmc.identity.responses.ListCompartmentsResponse;
+import com.oracle.bmc.model.BmcException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.TreeMap;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import org.netbeans.api.progress.ProgressHandle;
+import org.netbeans.api.project.Project;
+import org.netbeans.modules.cloud.oracle.OCIManager;
+import org.netbeans.modules.cloud.oracle.OCIProfile;
+import org.netbeans.modules.cloud.oracle.OCISessionInitiator;
+import org.netbeans.modules.cloud.oracle.bucket.BucketNode;
+import org.netbeans.modules.cloud.oracle.compartment.CompartmentItem;
+import org.netbeans.modules.cloud.oracle.compute.ClusterNode;
+import org.netbeans.modules.cloud.oracle.compute.ComputeInstanceNode;
+import org.netbeans.modules.cloud.oracle.database.DatabaseItem;
+import org.netbeans.modules.cloud.oracle.database.DatabaseNode;
+import org.netbeans.modules.cloud.oracle.items.OCID;
+import org.netbeans.modules.cloud.oracle.items.OCIItem;
+import org.netbeans.modules.cloud.oracle.items.TenancyItem;
+import org.netbeans.modules.cloud.oracle.vault.VaultNode;
+import org.openide.DialogDescriptor;
+import org.openide.DialogDisplayer;
+import org.openide.NotifyDescriptor;
+import org.openide.util.Exceptions;
+import org.openide.util.Lookup;
+import org.openide.util.Pair;
+import org.openide.util.RequestProcessor;
+
+/**
+ *
+ * @author Jan Horvath
+ */
+public final class Steps {
+    private static final Logger LOG = Logger.getLogger(Steps.class.getName());
+    private static Steps instance = null;
+
+    public static synchronized Steps getDefault() {
+        if (instance == null) {
+            instance = new Steps();
+        }
+        return instance;
+    }
+
+    public CompletableFuture<Object> executeMultistep(Step firstStep, Lookup 
lookup) {
+        DialogDisplayer dd = DialogDisplayer.getDefault();
+        CompletableFuture future = new CompletableFuture();
+        RequestProcessor.getDefault().post(() -> {
+            Multistep multistep = new Multistep(firstStep, lookup);
+            NotifyDescriptor.ComposedInput ci = new 
NotifyDescriptor.ComposedInput(Bundle.AddSuggestedItem(), 3, 
multistep.createInput());
+            if (DialogDescriptor.OK_OPTION == dd.notify(ci)) {
+                future.complete(multistep.getResult());
+            }
+        });
+        return future;
+    }
+
+    private static class Multistep {
+
+        private final LinkedList<Step> steps = new LinkedList<>();
+        private final Lookup lookup;
+
+        Multistep(Step firstStep, Lookup lookup) {
+            steps.add(firstStep);
+            this.lookup = lookup;
+        }
+
+        NotifyDescriptor.ComposedInput.Callback createInput() {
+            return new NotifyDescriptor.ComposedInput.Callback() {
+                private int lastNumber = 0;
+
+                private void readValue(Step step, NotifyDescriptor desc) {
+                    String selected = null;
+                    if (!step.onlyOneChoice()) {
+                        if (desc instanceof NotifyDescriptor.QuickPick) {
+                            for (NotifyDescriptor.QuickPick.Item item : 
((NotifyDescriptor.QuickPick) desc).getItems()) {
+                                if (item.isSelected()) {
+                                    selected = item.getLabel();
+                                    break;
+                                }
+                            }
+                        } else if (desc instanceof NotifyDescriptor.InputLine) 
{
+                            selected = ((NotifyDescriptor.InputLine) 
desc).getInputText();
+                        }
+                        step.setValue(selected);
+                    }
+                }
+
+                @Override
+                public NotifyDescriptor 
createInput(NotifyDescriptor.ComposedInput input, int number) {
+                    if (number == 1) {
+                        while (steps.size() > 1) {
+                            steps.removeLast();
+                        }
+                        steps.getLast().prepare(null, lookup);
+                    } else if (lastNumber > number) {
+                        steps.removeLast();
+                        while (steps.getLast().onlyOneChoice() && steps.size() 
> 1) {
+                            steps.removeLast();
+                        }
+                        lastNumber = number;
+                        return steps.getLast().createInput();
+                    } else {
+                        readValue(steps.getLast(), input.getInputs()[number - 
2]);
+                        steps.add(steps.getLast().getNext());
+                    }
+                    lastNumber = number;
+
+                    while (steps.getLast() != null && 
steps.getLast().onlyOneChoice()) {
+                        steps.add(steps.getLast().getNext());
+                    }
+                    if (steps.getLast() == null) {
+                        steps.removeLast();
+                        return null;
+                    }
+                    return steps.getLast().createInput();
+                }
+            };
+        }
+
+        public Object getResult() {
+            return steps.getLast().getValue();
+        }
+    }
+
+    static final class TenancyStep implements Step<Object, TenancyItem> {
+
+        List<OCIProfile> profiles = new LinkedList<>();
+        private AtomicReference<TenancyItem> selected = new 
AtomicReference<>();
+        private Lookup lookup;
+
+        @Override
+        public NotifyDescriptor createInput() {
+            if (onlyOneChoice()) {
+                throw new IllegalStateException("No data to create input"); // 
NOI18N
+            }
+            String title = Bundle.SelectProfile();
+            List<NotifyDescriptor.QuickPick.Item> items = new 
ArrayList<>(profiles.size());
+            for (OCIProfile p : profiles) {
+                Tenancy t = p.getTenancyData();
+                if (t != null) {
+                    items.add(new NotifyDescriptor.QuickPick.Item(p.getId(), 
Bundle.SelectProfile_Description(t.getName(), t.getHomeRegionKey())));
+                }
+            }
+            if (profiles.stream().filter(p -> 
p.getTenancy().isPresent()).count() == 0) {
+                title = Bundle.NoProfile();
+            }
+            return new NotifyDescriptor.QuickPick(title, title, items, false);
+        }
+
+        @Override
+        public Step getNext() {
+            return new CompartmentStep().prepare(getValue(), lookup);
+        }
+
+        @Override
+        public Step<Object, TenancyItem> prepare(Object i, Lookup lookup) {
+            this.lookup = lookup;
+            ProgressHandle h = 
ProgressHandle.createHandle(Bundle.CollectingProfiles());
+            h.start();
+            h.progress(Bundle.CollectingProfiles_Text());
+            try {
+                profiles = OCIManager.getDefault().getConnectedProfiles();
+            } finally {
+                h.finish();
+            }
+            return this;
+        }
+
+        public void setValue(String value) {
+            for (OCIProfile profile : profiles) {
+                if (profile.getId().equals(value)) {
+                    profile.getTenancy().ifPresent(t -> this.selected.set(t));
+                    OCIManager.getDefault().setActiveProfile(profile);

Review Comment:
   Is desired that an action sets a system-wide active profile ?



##########
enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/Steps.java:
##########
@@ -0,0 +1,656 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.cloud.oracle.assets;
+
+import com.oracle.bmc.identity.Identity;
+import com.oracle.bmc.identity.IdentityClient;
+import com.oracle.bmc.identity.model.Compartment;
+import com.oracle.bmc.identity.model.Tenancy;
+import com.oracle.bmc.identity.requests.ListCompartmentsRequest;
+import com.oracle.bmc.identity.responses.ListCompartmentsResponse;
+import com.oracle.bmc.model.BmcException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.TreeMap;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import org.netbeans.api.progress.ProgressHandle;
+import org.netbeans.api.project.Project;
+import org.netbeans.modules.cloud.oracle.OCIManager;
+import org.netbeans.modules.cloud.oracle.OCIProfile;
+import org.netbeans.modules.cloud.oracle.OCISessionInitiator;
+import org.netbeans.modules.cloud.oracle.bucket.BucketNode;
+import org.netbeans.modules.cloud.oracle.compartment.CompartmentItem;
+import org.netbeans.modules.cloud.oracle.compute.ClusterNode;
+import org.netbeans.modules.cloud.oracle.compute.ComputeInstanceNode;
+import org.netbeans.modules.cloud.oracle.database.DatabaseItem;
+import org.netbeans.modules.cloud.oracle.database.DatabaseNode;
+import org.netbeans.modules.cloud.oracle.items.OCID;
+import org.netbeans.modules.cloud.oracle.items.OCIItem;
+import org.netbeans.modules.cloud.oracle.items.TenancyItem;
+import org.netbeans.modules.cloud.oracle.vault.VaultNode;
+import org.openide.DialogDescriptor;
+import org.openide.DialogDisplayer;
+import org.openide.NotifyDescriptor;
+import org.openide.util.Exceptions;
+import org.openide.util.Lookup;
+import org.openide.util.Pair;
+import org.openide.util.RequestProcessor;
+
+/**
+ *
+ * @author Jan Horvath
+ */
+public final class Steps {
+    private static final Logger LOG = Logger.getLogger(Steps.class.getName());
+    private static Steps instance = null;
+
+    public static synchronized Steps getDefault() {
+        if (instance == null) {
+            instance = new Steps();
+        }
+        return instance;
+    }
+
+    public CompletableFuture<Object> executeMultistep(Step firstStep, Lookup 
lookup) {
+        DialogDisplayer dd = DialogDisplayer.getDefault();
+        CompletableFuture future = new CompletableFuture();
+        RequestProcessor.getDefault().post(() -> {
+            Multistep multistep = new Multistep(firstStep, lookup);
+            NotifyDescriptor.ComposedInput ci = new 
NotifyDescriptor.ComposedInput(Bundle.AddSuggestedItem(), 3, 
multistep.createInput());
+            if (DialogDescriptor.OK_OPTION == dd.notify(ci)) {
+                future.complete(multistep.getResult());
+            }
+        });
+        return future;
+    }
+
+    private static class Multistep {
+
+        private final LinkedList<Step> steps = new LinkedList<>();
+        private final Lookup lookup;
+
+        Multistep(Step firstStep, Lookup lookup) {
+            steps.add(firstStep);
+            this.lookup = lookup;
+        }
+
+        NotifyDescriptor.ComposedInput.Callback createInput() {
+            return new NotifyDescriptor.ComposedInput.Callback() {
+                private int lastNumber = 0;
+
+                private void readValue(Step step, NotifyDescriptor desc) {
+                    String selected = null;
+                    if (!step.onlyOneChoice()) {
+                        if (desc instanceof NotifyDescriptor.QuickPick) {
+                            for (NotifyDescriptor.QuickPick.Item item : 
((NotifyDescriptor.QuickPick) desc).getItems()) {
+                                if (item.isSelected()) {
+                                    selected = item.getLabel();
+                                    break;
+                                }
+                            }
+                        } else if (desc instanceof NotifyDescriptor.InputLine) 
{
+                            selected = ((NotifyDescriptor.InputLine) 
desc).getInputText();
+                        }
+                        step.setValue(selected);
+                    }
+                }
+
+                @Override
+                public NotifyDescriptor 
createInput(NotifyDescriptor.ComposedInput input, int number) {
+                    if (number == 1) {
+                        while (steps.size() > 1) {
+                            steps.removeLast();
+                        }
+                        steps.getLast().prepare(null, lookup);
+                    } else if (lastNumber > number) {
+                        steps.removeLast();
+                        while (steps.getLast().onlyOneChoice() && steps.size() 
> 1) {
+                            steps.removeLast();
+                        }
+                        lastNumber = number;
+                        return steps.getLast().createInput();
+                    } else {
+                        readValue(steps.getLast(), input.getInputs()[number - 
2]);
+                        steps.add(steps.getLast().getNext());
+                    }
+                    lastNumber = number;
+
+                    while (steps.getLast() != null && 
steps.getLast().onlyOneChoice()) {
+                        steps.add(steps.getLast().getNext());
+                    }
+                    if (steps.getLast() == null) {
+                        steps.removeLast();
+                        return null;
+                    }
+                    return steps.getLast().createInput();
+                }
+            };
+        }
+
+        public Object getResult() {
+            return steps.getLast().getValue();
+        }
+    }
+
+    static final class TenancyStep implements Step<Object, TenancyItem> {
+
+        List<OCIProfile> profiles = new LinkedList<>();
+        private AtomicReference<TenancyItem> selected = new 
AtomicReference<>();
+        private Lookup lookup;
+
+        @Override
+        public NotifyDescriptor createInput() {
+            if (onlyOneChoice()) {
+                throw new IllegalStateException("No data to create input"); // 
NOI18N
+            }
+            String title = Bundle.SelectProfile();
+            List<NotifyDescriptor.QuickPick.Item> items = new 
ArrayList<>(profiles.size());
+            for (OCIProfile p : profiles) {
+                Tenancy t = p.getTenancyData();
+                if (t != null) {
+                    items.add(new NotifyDescriptor.QuickPick.Item(p.getId(), 
Bundle.SelectProfile_Description(t.getName(), t.getHomeRegionKey())));
+                }
+            }
+            if (profiles.stream().filter(p -> 
p.getTenancy().isPresent()).count() == 0) {
+                title = Bundle.NoProfile();
+            }
+            return new NotifyDescriptor.QuickPick(title, title, items, false);
+        }
+
+        @Override
+        public Step getNext() {
+            return new CompartmentStep().prepare(getValue(), lookup);
+        }
+
+        @Override
+        public Step<Object, TenancyItem> prepare(Object i, Lookup lookup) {
+            this.lookup = lookup;
+            ProgressHandle h = 
ProgressHandle.createHandle(Bundle.CollectingProfiles());
+            h.start();
+            h.progress(Bundle.CollectingProfiles_Text());
+            try {
+                profiles = OCIManager.getDefault().getConnectedProfiles();
+            } finally {
+                h.finish();
+            }
+            return this;
+        }
+
+        public void setValue(String value) {
+            for (OCIProfile profile : profiles) {
+                if (profile.getId().equals(value)) {
+                    profile.getTenancy().ifPresent(t -> this.selected.set(t));
+                    OCIManager.getDefault().setActiveProfile(profile);
+                    break;
+                }
+            }
+        }
+
+        @Override
+        public TenancyItem getValue() {
+            if (onlyOneChoice()) {
+                return profiles.stream()
+                        .map(p -> p.getTenancy())
+                        .filter(Optional::isPresent)
+                        .map(Optional::get)
+                        .findFirst()
+                        .get();
+            }
+            return selected.get();
+        }
+
+        @Override
+        public boolean onlyOneChoice() {
+            return profiles.stream().filter(p -> 
p.getTenancy().isPresent()).count() == 1;
+        }
+    }
+
+    static final class CompartmentStep implements Step<TenancyItem, 
CompartmentItem> {
+
+        private Map<String, OCIItem> compartments = null;
+        private CompartmentItem selected;
+        private Lookup lookup;
+
+        public Step<TenancyItem, CompartmentItem> prepare(TenancyItem tenancy, 
Lookup lookup) {
+            this.lookup = lookup;
+            ProgressHandle h = 
ProgressHandle.createHandle(Bundle.CollectingItems());
+            h.start();
+            h.progress(Bundle.CollectingItems_Text());
+            try {
+                compartments = getFlatCompartment(tenancy);
+            } finally {
+                h.finish();
+            }
+            return this;
+        }
+
+        @Override
+        public NotifyDescriptor createInput() {
+            if (onlyOneChoice()) {
+                throw new IllegalStateException("Input shouldn't be displayed 
for one choice"); // NOI18N
+            }
+            if (compartments.isEmpty()) {
+                return createQuickPick(compartments, Bundle.NoCompartment());
+            }
+            return createQuickPick(compartments, Bundle.SelectCompartment());
+        }
+
+        @Override
+        public Step getNext() {
+            return new SuggestedStep().prepare(getValue(), lookup);
+        }
+
+        @Override
+        public void setValue(String selected) {
+            this.selected = (CompartmentItem) compartments.get(selected);
+        }
+
+        @Override
+        public CompartmentItem getValue() {
+            if (onlyOneChoice()) {
+                return (CompartmentItem) 
compartments.values().iterator().next();
+            }
+            return selected;
+        }
+
+        @Override
+        public boolean onlyOneChoice() {
+            return compartments.size() == 1;
+        }
+    }
+
+    /**
+     * Context of SuggestedStep. Determines next step.
+     */
+    public interface SuggestedContext {
+
+        String getItemType();
+
+        Step getNextStep();
+
+        public void setItemType(String selected);
+    }
+
+    /**
+     * Show list of items for a suggested type.
+     * 
+     */
+    static class SuggestedStep implements Step<CompartmentItem, OCIItem> {
+
+        private Map<String, OCIItem> items = new HashMap<>();
+        private OCIItem selected;
+        private Lookup lookup;
+        private SuggestedContext context = null;
+
+        public SuggestedStep prepare(CompartmentItem compartment, Lookup 
lookup) {
+            this.lookup = lookup;
+            context = lookup.lookup(SuggestedContext.class);
+            ProgressHandle h = 
ProgressHandle.createHandle(Bundle.CollectingItems());
+            h.start();
+            h.progress(Bundle.CollectingItems_Text());
+            try {
+                getItemsByPath(compartment, 
context.getItemType()).forEach((db) -> items.put(db.getName(), db));
+            } finally {
+                h.finish();
+            }
+            return this;
+        }
+
+        private String getSuggestedItemName() {
+            switch (context.getItemType()) {
+                case "Databases":
+                    return "Oracle Autonomous Database";
+                case "Vault":
+                    return "OCI Vault";
+                case "Bucket":
+                    return "Bucket";
+                case "Cluster":
+                    return "Cluster";
+                case "ComputeInstance":
+                    return "ComputeInstance";
+                default:
+                    return "X";
+            }
+        }
+
+        @Override
+        public NotifyDescriptor createInput() {
+            return createQuickPick(items, 
Bundle.SelectItem(getSuggestedItemName()));
+        }
+
+        @Override
+        public Step getNext() {
+            Step next = context.getNextStep();
+            if (next != null) {
+                next.prepare(getValue(), lookup);
+            }
+            return next;
+        }
+
+        @Override
+        public void setValue(String selected) {
+            this.selected = items.get(selected);
+        }
+
+        @Override
+        public OCIItem getValue() {
+            if (onlyOneChoice()) {
+                selected = items.values().iterator().next();
+            }
+            return selected;
+        }
+
+        @Override
+        public boolean onlyOneChoice() {
+            return false;
+        }
+    }
+
+    private static <T extends OCIItem> NotifyDescriptor.QuickPick 
createQuickPick(Map<String, T> ociItems, String title) {
+        List<NotifyDescriptor.QuickPick.Item> items = 
ociItems.entrySet().stream()
+                .map(entry -> new 
NotifyDescriptor.QuickPick.Item(entry.getKey(), 
entry.getValue().getDescription()))
+                .collect(Collectors.toList());
+        return new NotifyDescriptor.QuickPick(title, title, items, false);
+    }
+
+    /**
+     * Retrieve all compartments from a tenancy.
+     * 
+     * @param tenancy
+     * @return 
+     */
+    static private Map<String, OCIItem> getFlatCompartment(TenancyItem 
tenancy) {
+        Map<OCID, FlatCompartmentItem> compartments = new HashMap<>();
+        OCISessionInitiator session = 
OCIManager.getDefault().getActiveSession();
+        Identity identityClient = session.newClient(IdentityClient.class);
+        String nextPageToken = null;
+
+        do {
+            ListCompartmentsResponse response
+                    = identityClient.listCompartments(
+                            ListCompartmentsRequest.builder()
+                                    .compartmentId(tenancy.getKey().getValue())
+                                    .compartmentIdInSubtree(true)
+                                    
.lifecycleState(Compartment.LifecycleState.Active)
+                                    
.accessLevel(ListCompartmentsRequest.AccessLevel.Accessible)
+                                    .limit(1000)
+                                    .page(nextPageToken)
+                                    .build());
+            for (Compartment comp : response.getItems()) {
+                FlatCompartmentItem ci = new FlatCompartmentItem(comp) {
+                    FlatCompartmentItem getItem(OCID compId) {
+                        return compartments.get(compId);
+                    }
+                };
+                compartments.put(ci.getKey(), ci);
+            }
+            nextPageToken = response.getOpcNextPage();
+        } while (nextPageToken != null);
+        Map<String, OCIItem> pickItems = computeFlatNames(compartments);
+        pickItems.put(tenancy.getName() + " (root)", tenancy);        // NOI18N
+        return pickItems;
+    }
+    
+    static private Map<String, OCIItem> computeFlatNames(Map<OCID, 
FlatCompartmentItem> compartments) {
+        Map<String, OCIItem> pickItems = new 
TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+        for (FlatCompartmentItem comp : compartments.values()) {
+            pickItems.put(comp.getName(), comp);
+        }
+        return pickItems;
+    }
+
+    /**
+     * This class represents compartments in a flat structure. Individual 
levels are separated by slashes.
+     * 
+     */
+    static private abstract class FlatCompartmentItem extends CompartmentItem {
+
+        private final OCID parentId;
+        private String flatName;
+
+        private FlatCompartmentItem(Compartment ociComp) {
+            super(OCID.of(ociComp.getId(), "Compartment"), 
ociComp.getCompartmentId(), ociComp.getName());      // NOI18N
+            setDescription(ociComp.getDescription());
+            parentId = OCID.of(ociComp.getCompartmentId(), "Compartment"); // 
NOI18N
+        }
+
+        public String getName() {
+            if (parentId.getValue() == null) {
+                return "";
+            }
+            if (flatName == null) {
+                String parentFlatName = "";
+                FlatCompartmentItem parentComp = getItem(parentId);
+                if (parentComp != null) {
+                    parentFlatName = parentComp.getName();
+                }
+                flatName = super.getName();
+                if (!parentFlatName.isEmpty()) {
+                    flatName = parentFlatName + "/" + flatName;  // NOI18N
+                }
+            }
+            return flatName;
+        }
+
+        abstract FlatCompartmentItem getItem(OCID compId);
+    }
+    
+    /**
+     *  This step allows the user to select which type of resource will be 
added.
+     */
+    static class ItemTypeStep implements Step<Object, String> {
+        String[] types = {"Databases", "Vault", "Bucket"}; //NOI18N
+
+        private Lookup lookup;
+        private SuggestedContext context;
+
+        @Override
+        public Step<Object, String> prepare(Object item, Lookup lookup) {
+            context = lookup.lookup(SuggestedContext.class);
+            this.lookup = lookup;
+            return this;
+        }
+
+        @Override
+        public NotifyDescriptor createInput() {
+            List<NotifyDescriptor.QuickPick.Item> items = new 
ArrayList<>(types.length);
+            for (String itemType : types) {
+                items.add(new NotifyDescriptor.QuickPick.Item(itemType, 
itemType));

Review Comment:
   labels should be localized.



##########
enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/Steps.java:
##########
@@ -0,0 +1,656 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.cloud.oracle.assets;
+
+import com.oracle.bmc.identity.Identity;
+import com.oracle.bmc.identity.IdentityClient;
+import com.oracle.bmc.identity.model.Compartment;
+import com.oracle.bmc.identity.model.Tenancy;
+import com.oracle.bmc.identity.requests.ListCompartmentsRequest;
+import com.oracle.bmc.identity.responses.ListCompartmentsResponse;
+import com.oracle.bmc.model.BmcException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.TreeMap;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import org.netbeans.api.progress.ProgressHandle;
+import org.netbeans.api.project.Project;
+import org.netbeans.modules.cloud.oracle.OCIManager;
+import org.netbeans.modules.cloud.oracle.OCIProfile;
+import org.netbeans.modules.cloud.oracle.OCISessionInitiator;
+import org.netbeans.modules.cloud.oracle.bucket.BucketNode;
+import org.netbeans.modules.cloud.oracle.compartment.CompartmentItem;
+import org.netbeans.modules.cloud.oracle.compute.ClusterNode;
+import org.netbeans.modules.cloud.oracle.compute.ComputeInstanceNode;
+import org.netbeans.modules.cloud.oracle.database.DatabaseItem;
+import org.netbeans.modules.cloud.oracle.database.DatabaseNode;
+import org.netbeans.modules.cloud.oracle.items.OCID;
+import org.netbeans.modules.cloud.oracle.items.OCIItem;
+import org.netbeans.modules.cloud.oracle.items.TenancyItem;
+import org.netbeans.modules.cloud.oracle.vault.VaultNode;
+import org.openide.DialogDescriptor;
+import org.openide.DialogDisplayer;
+import org.openide.NotifyDescriptor;
+import org.openide.util.Exceptions;
+import org.openide.util.Lookup;
+import org.openide.util.Pair;
+import org.openide.util.RequestProcessor;
+
+/**
+ *
+ * @author Jan Horvath
+ */
+public final class Steps {
+    private static final Logger LOG = Logger.getLogger(Steps.class.getName());
+    private static Steps instance = null;
+
+    public static synchronized Steps getDefault() {
+        if (instance == null) {
+            instance = new Steps();
+        }
+        return instance;
+    }
+
+    public CompletableFuture<Object> executeMultistep(Step firstStep, Lookup 
lookup) {
+        DialogDisplayer dd = DialogDisplayer.getDefault();
+        CompletableFuture future = new CompletableFuture();
+        RequestProcessor.getDefault().post(() -> {
+            Multistep multistep = new Multistep(firstStep, lookup);
+            NotifyDescriptor.ComposedInput ci = new 
NotifyDescriptor.ComposedInput(Bundle.AddSuggestedItem(), 3, 
multistep.createInput());
+            if (DialogDescriptor.OK_OPTION == dd.notify(ci)) {
+                future.complete(multistep.getResult());
+            }
+        });
+        return future;
+    }
+
+    private static class Multistep {
+
+        private final LinkedList<Step> steps = new LinkedList<>();
+        private final Lookup lookup;
+
+        Multistep(Step firstStep, Lookup lookup) {
+            steps.add(firstStep);
+            this.lookup = lookup;
+        }
+
+        NotifyDescriptor.ComposedInput.Callback createInput() {
+            return new NotifyDescriptor.ComposedInput.Callback() {
+                private int lastNumber = 0;
+
+                private void readValue(Step step, NotifyDescriptor desc) {
+                    String selected = null;
+                    if (!step.onlyOneChoice()) {
+                        if (desc instanceof NotifyDescriptor.QuickPick) {
+                            for (NotifyDescriptor.QuickPick.Item item : 
((NotifyDescriptor.QuickPick) desc).getItems()) {
+                                if (item.isSelected()) {
+                                    selected = item.getLabel();
+                                    break;
+                                }
+                            }
+                        } else if (desc instanceof NotifyDescriptor.InputLine) 
{
+                            selected = ((NotifyDescriptor.InputLine) 
desc).getInputText();
+                        }
+                        step.setValue(selected);
+                    }
+                }
+
+                @Override
+                public NotifyDescriptor 
createInput(NotifyDescriptor.ComposedInput input, int number) {
+                    if (number == 1) {
+                        while (steps.size() > 1) {
+                            steps.removeLast();
+                        }
+                        steps.getLast().prepare(null, lookup);
+                    } else if (lastNumber > number) {
+                        steps.removeLast();
+                        while (steps.getLast().onlyOneChoice() && steps.size() 
> 1) {
+                            steps.removeLast();
+                        }
+                        lastNumber = number;
+                        return steps.getLast().createInput();
+                    } else {
+                        readValue(steps.getLast(), input.getInputs()[number - 
2]);

Review Comment:
   so for number == 2 (lastNumber == 1), this will read from getInputs()[0] ?



##########
java/java.lsp.server/vscode/src/extension.ts:
##########
@@ -1671,3 +1692,31 @@ class NetBeansConfigurationNativeResolver implements 
vscode.DebugConfigurationPr
         return config;
     }
 }
+
+
+class StringContentProvider implements vscode.TextDocumentContentProvider {

Review Comment:
   Seems that this Provider is unused ... ? It's possible that I overlooked 
something :( or maybe there are some more recent changes ?



##########
enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/Steps.java:
##########
@@ -0,0 +1,656 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.cloud.oracle.assets;
+
+import com.oracle.bmc.identity.Identity;
+import com.oracle.bmc.identity.IdentityClient;
+import com.oracle.bmc.identity.model.Compartment;
+import com.oracle.bmc.identity.model.Tenancy;
+import com.oracle.bmc.identity.requests.ListCompartmentsRequest;
+import com.oracle.bmc.identity.responses.ListCompartmentsResponse;
+import com.oracle.bmc.model.BmcException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.TreeMap;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import org.netbeans.api.progress.ProgressHandle;
+import org.netbeans.api.project.Project;
+import org.netbeans.modules.cloud.oracle.OCIManager;
+import org.netbeans.modules.cloud.oracle.OCIProfile;
+import org.netbeans.modules.cloud.oracle.OCISessionInitiator;
+import org.netbeans.modules.cloud.oracle.bucket.BucketNode;
+import org.netbeans.modules.cloud.oracle.compartment.CompartmentItem;
+import org.netbeans.modules.cloud.oracle.compute.ClusterNode;
+import org.netbeans.modules.cloud.oracle.compute.ComputeInstanceNode;
+import org.netbeans.modules.cloud.oracle.database.DatabaseItem;
+import org.netbeans.modules.cloud.oracle.database.DatabaseNode;
+import org.netbeans.modules.cloud.oracle.items.OCID;
+import org.netbeans.modules.cloud.oracle.items.OCIItem;
+import org.netbeans.modules.cloud.oracle.items.TenancyItem;
+import org.netbeans.modules.cloud.oracle.vault.VaultNode;
+import org.openide.DialogDescriptor;
+import org.openide.DialogDisplayer;
+import org.openide.NotifyDescriptor;
+import org.openide.util.Exceptions;
+import org.openide.util.Lookup;
+import org.openide.util.Pair;
+import org.openide.util.RequestProcessor;
+
+/**
+ *
+ * @author Jan Horvath
+ */
+public final class Steps {
+    private static final Logger LOG = Logger.getLogger(Steps.class.getName());
+    private static Steps instance = null;
+
+    public static synchronized Steps getDefault() {
+        if (instance == null) {
+            instance = new Steps();
+        }
+        return instance;
+    }
+
+    public CompletableFuture<Object> executeMultistep(Step firstStep, Lookup 
lookup) {
+        DialogDisplayer dd = DialogDisplayer.getDefault();
+        CompletableFuture future = new CompletableFuture();
+        RequestProcessor.getDefault().post(() -> {
+            Multistep multistep = new Multistep(firstStep, lookup);
+            NotifyDescriptor.ComposedInput ci = new 
NotifyDescriptor.ComposedInput(Bundle.AddSuggestedItem(), 3, 
multistep.createInput());
+            if (DialogDescriptor.OK_OPTION == dd.notify(ci)) {
+                future.complete(multistep.getResult());
+            }
+        });
+        return future;
+    }
+
+    private static class Multistep {
+
+        private final LinkedList<Step> steps = new LinkedList<>();
+        private final Lookup lookup;
+
+        Multistep(Step firstStep, Lookup lookup) {
+            steps.add(firstStep);
+            this.lookup = lookup;
+        }
+
+        NotifyDescriptor.ComposedInput.Callback createInput() {
+            return new NotifyDescriptor.ComposedInput.Callback() {
+                private int lastNumber = 0;
+
+                private void readValue(Step step, NotifyDescriptor desc) {
+                    String selected = null;
+                    if (!step.onlyOneChoice()) {
+                        if (desc instanceof NotifyDescriptor.QuickPick) {
+                            for (NotifyDescriptor.QuickPick.Item item : 
((NotifyDescriptor.QuickPick) desc).getItems()) {
+                                if (item.isSelected()) {
+                                    selected = item.getLabel();
+                                    break;
+                                }
+                            }
+                        } else if (desc instanceof NotifyDescriptor.InputLine) 
{
+                            selected = ((NotifyDescriptor.InputLine) 
desc).getInputText();
+                        }
+                        step.setValue(selected);
+                    }
+                }
+
+                @Override
+                public NotifyDescriptor 
createInput(NotifyDescriptor.ComposedInput input, int number) {
+                    if (number == 1) {
+                        while (steps.size() > 1) {
+                            steps.removeLast();
+                        }
+                        steps.getLast().prepare(null, lookup);
+                    } else if (lastNumber > number) {
+                        steps.removeLast();
+                        while (steps.getLast().onlyOneChoice() && steps.size() 
> 1) {
+                            steps.removeLast();
+                        }
+                        lastNumber = number;
+                        return steps.getLast().createInput();
+                    } else {
+                        readValue(steps.getLast(), input.getInputs()[number - 
2]);
+                        steps.add(steps.getLast().getNext());
+                    }
+                    lastNumber = number;
+
+                    while (steps.getLast() != null && 
steps.getLast().onlyOneChoice()) {
+                        steps.add(steps.getLast().getNext());
+                    }
+                    if (steps.getLast() == null) {
+                        steps.removeLast();
+                        return null;
+                    }
+                    return steps.getLast().createInput();
+                }
+            };
+        }
+
+        public Object getResult() {
+            return steps.getLast().getValue();
+        }
+    }
+
+    static final class TenancyStep implements Step<Object, TenancyItem> {
+
+        List<OCIProfile> profiles = new LinkedList<>();
+        private AtomicReference<TenancyItem> selected = new 
AtomicReference<>();
+        private Lookup lookup;
+
+        @Override
+        public NotifyDescriptor createInput() {
+            if (onlyOneChoice()) {
+                throw new IllegalStateException("No data to create input"); // 
NOI18N
+            }
+            String title = Bundle.SelectProfile();
+            List<NotifyDescriptor.QuickPick.Item> items = new 
ArrayList<>(profiles.size());
+            for (OCIProfile p : profiles) {
+                Tenancy t = p.getTenancyData();
+                if (t != null) {
+                    items.add(new NotifyDescriptor.QuickPick.Item(p.getId(), 
Bundle.SelectProfile_Description(t.getName(), t.getHomeRegionKey())));
+                }
+            }
+            if (profiles.stream().filter(p -> 
p.getTenancy().isPresent()).count() == 0) {
+                title = Bundle.NoProfile();
+            }
+            return new NotifyDescriptor.QuickPick(title, title, items, false);
+        }
+
+        @Override
+        public Step getNext() {
+            return new CompartmentStep().prepare(getValue(), lookup);
+        }
+
+        @Override
+        public Step<Object, TenancyItem> prepare(Object i, Lookup lookup) {
+            this.lookup = lookup;
+            ProgressHandle h = 
ProgressHandle.createHandle(Bundle.CollectingProfiles());
+            h.start();
+            h.progress(Bundle.CollectingProfiles_Text());
+            try {
+                profiles = OCIManager.getDefault().getConnectedProfiles();
+            } finally {
+                h.finish();
+            }
+            return this;
+        }
+
+        public void setValue(String value) {
+            for (OCIProfile profile : profiles) {
+                if (profile.getId().equals(value)) {
+                    profile.getTenancy().ifPresent(t -> this.selected.set(t));
+                    OCIManager.getDefault().setActiveProfile(profile);
+                    break;
+                }
+            }
+        }
+
+        @Override
+        public TenancyItem getValue() {
+            if (onlyOneChoice()) {
+                return profiles.stream()
+                        .map(p -> p.getTenancy())
+                        .filter(Optional::isPresent)
+                        .map(Optional::get)
+                        .findFirst()
+                        .get();
+            }
+            return selected.get();
+        }
+
+        @Override
+        public boolean onlyOneChoice() {
+            return profiles.stream().filter(p -> 
p.getTenancy().isPresent()).count() == 1;
+        }
+    }
+
+    static final class CompartmentStep implements Step<TenancyItem, 
CompartmentItem> {
+
+        private Map<String, OCIItem> compartments = null;
+        private CompartmentItem selected;
+        private Lookup lookup;
+
+        public Step<TenancyItem, CompartmentItem> prepare(TenancyItem tenancy, 
Lookup lookup) {
+            this.lookup = lookup;
+            ProgressHandle h = 
ProgressHandle.createHandle(Bundle.CollectingItems());
+            h.start();
+            h.progress(Bundle.CollectingItems_Text());
+            try {
+                compartments = getFlatCompartment(tenancy);
+            } finally {
+                h.finish();
+            }
+            return this;
+        }
+
+        @Override
+        public NotifyDescriptor createInput() {
+            if (onlyOneChoice()) {
+                throw new IllegalStateException("Input shouldn't be displayed 
for one choice"); // NOI18N
+            }
+            if (compartments.isEmpty()) {
+                return createQuickPick(compartments, Bundle.NoCompartment());
+            }
+            return createQuickPick(compartments, Bundle.SelectCompartment());
+        }
+
+        @Override
+        public Step getNext() {
+            return new SuggestedStep().prepare(getValue(), lookup);
+        }
+
+        @Override
+        public void setValue(String selected) {
+            this.selected = (CompartmentItem) compartments.get(selected);
+        }
+
+        @Override
+        public CompartmentItem getValue() {
+            if (onlyOneChoice()) {
+                return (CompartmentItem) 
compartments.values().iterator().next();
+            }
+            return selected;
+        }
+
+        @Override
+        public boolean onlyOneChoice() {
+            return compartments.size() == 1;
+        }
+    }
+
+    /**
+     * Context of SuggestedStep. Determines next step.
+     */
+    public interface SuggestedContext {
+
+        String getItemType();
+
+        Step getNextStep();
+
+        public void setItemType(String selected);
+    }
+
+    /**
+     * Show list of items for a suggested type.
+     * 
+     */
+    static class SuggestedStep implements Step<CompartmentItem, OCIItem> {
+
+        private Map<String, OCIItem> items = new HashMap<>();
+        private OCIItem selected;
+        private Lookup lookup;
+        private SuggestedContext context = null;
+
+        public SuggestedStep prepare(CompartmentItem compartment, Lookup 
lookup) {
+            this.lookup = lookup;
+            context = lookup.lookup(SuggestedContext.class);
+            ProgressHandle h = 
ProgressHandle.createHandle(Bundle.CollectingItems());
+            h.start();
+            h.progress(Bundle.CollectingItems_Text());
+            try {
+                getItemsByPath(compartment, 
context.getItemType()).forEach((db) -> items.put(db.getName(), db));
+            } finally {
+                h.finish();
+            }
+            return this;
+        }
+
+        private String getSuggestedItemName() {
+            switch (context.getItemType()) {
+                case "Databases":
+                    return "Oracle Autonomous Database";
+                case "Vault":
+                    return "OCI Vault";
+                case "Bucket":
+                    return "Bucket";
+                case "Cluster":
+                    return "Cluster";
+                case "ComputeInstance":
+                    return "ComputeInstance";
+                default:
+                    return "X";
+            }
+        }
+
+        @Override
+        public NotifyDescriptor createInput() {
+            return createQuickPick(items, 
Bundle.SelectItem(getSuggestedItemName()));
+        }
+
+        @Override
+        public Step getNext() {
+            Step next = context.getNextStep();
+            if (next != null) {
+                next.prepare(getValue(), lookup);
+            }
+            return next;
+        }
+
+        @Override
+        public void setValue(String selected) {
+            this.selected = items.get(selected);
+        }
+
+        @Override
+        public OCIItem getValue() {
+            if (onlyOneChoice()) {
+                selected = items.values().iterator().next();
+            }
+            return selected;
+        }
+
+        @Override
+        public boolean onlyOneChoice() {
+            return false;
+        }
+    }
+
+    private static <T extends OCIItem> NotifyDescriptor.QuickPick 
createQuickPick(Map<String, T> ociItems, String title) {
+        List<NotifyDescriptor.QuickPick.Item> items = 
ociItems.entrySet().stream()
+                .map(entry -> new 
NotifyDescriptor.QuickPick.Item(entry.getKey(), 
entry.getValue().getDescription()))
+                .collect(Collectors.toList());
+        return new NotifyDescriptor.QuickPick(title, title, items, false);
+    }
+
+    /**
+     * Retrieve all compartments from a tenancy.
+     * 
+     * @param tenancy
+     * @return 
+     */
+    static private Map<String, OCIItem> getFlatCompartment(TenancyItem 
tenancy) {
+        Map<OCID, FlatCompartmentItem> compartments = new HashMap<>();
+        OCISessionInitiator session = 
OCIManager.getDefault().getActiveSession();
+        Identity identityClient = session.newClient(IdentityClient.class);
+        String nextPageToken = null;
+
+        do {
+            ListCompartmentsResponse response
+                    = identityClient.listCompartments(
+                            ListCompartmentsRequest.builder()
+                                    .compartmentId(tenancy.getKey().getValue())
+                                    .compartmentIdInSubtree(true)
+                                    
.lifecycleState(Compartment.LifecycleState.Active)
+                                    
.accessLevel(ListCompartmentsRequest.AccessLevel.Accessible)
+                                    .limit(1000)
+                                    .page(nextPageToken)
+                                    .build());
+            for (Compartment comp : response.getItems()) {
+                FlatCompartmentItem ci = new FlatCompartmentItem(comp) {
+                    FlatCompartmentItem getItem(OCID compId) {
+                        return compartments.get(compId);
+                    }
+                };
+                compartments.put(ci.getKey(), ci);
+            }
+            nextPageToken = response.getOpcNextPage();
+        } while (nextPageToken != null);
+        Map<String, OCIItem> pickItems = computeFlatNames(compartments);
+        pickItems.put(tenancy.getName() + " (root)", tenancy);        // NOI18N
+        return pickItems;
+    }
+    
+    static private Map<String, OCIItem> computeFlatNames(Map<OCID, 
FlatCompartmentItem> compartments) {
+        Map<String, OCIItem> pickItems = new 
TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+        for (FlatCompartmentItem comp : compartments.values()) {
+            pickItems.put(comp.getName(), comp);
+        }
+        return pickItems;
+    }
+
+    /**
+     * This class represents compartments in a flat structure. Individual 
levels are separated by slashes.
+     * 
+     */
+    static private abstract class FlatCompartmentItem extends CompartmentItem {
+
+        private final OCID parentId;
+        private String flatName;
+
+        private FlatCompartmentItem(Compartment ociComp) {
+            super(OCID.of(ociComp.getId(), "Compartment"), 
ociComp.getCompartmentId(), ociComp.getName());      // NOI18N
+            setDescription(ociComp.getDescription());
+            parentId = OCID.of(ociComp.getCompartmentId(), "Compartment"); // 
NOI18N
+        }
+
+        public String getName() {
+            if (parentId.getValue() == null) {
+                return "";
+            }
+            if (flatName == null) {
+                String parentFlatName = "";
+                FlatCompartmentItem parentComp = getItem(parentId);
+                if (parentComp != null) {
+                    parentFlatName = parentComp.getName();
+                }
+                flatName = super.getName();
+                if (!parentFlatName.isEmpty()) {
+                    flatName = parentFlatName + "/" + flatName;  // NOI18N
+                }
+            }
+            return flatName;
+        }
+
+        abstract FlatCompartmentItem getItem(OCID compId);
+    }
+    
+    /**
+     *  This step allows the user to select which type of resource will be 
added.
+     */
+    static class ItemTypeStep implements Step<Object, String> {
+        String[] types = {"Databases", "Vault", "Bucket"}; //NOI18N
+
+        private Lookup lookup;
+        private SuggestedContext context;
+
+        @Override
+        public Step<Object, String> prepare(Object item, Lookup lookup) {
+            context = lookup.lookup(SuggestedContext.class);
+            this.lookup = lookup;
+            return this;
+        }
+
+        @Override
+        public NotifyDescriptor createInput() {
+            List<NotifyDescriptor.QuickPick.Item> items = new 
ArrayList<>(types.length);
+            for (String itemType : types) {
+                items.add(new NotifyDescriptor.QuickPick.Item(itemType, 
itemType));
+            }
+            return new NotifyDescriptor.QuickPick(Bundle.SelectResourceType(), 
Bundle.SelectResourceType(), items, false);
+        }
+
+        @Override
+        public boolean onlyOneChoice() {
+            return false;
+        }
+
+        @Override
+        public Step getNext() {
+            return new TenancyStep().prepare(null, lookup);
+        }
+
+        @Override
+        public void setValue(String selected) {
+            context.setItemType(selected);
+        }
+
+        @Override
+        public String getValue() {
+            return context.getItemType();
+        }
+
+    }
+
+    /**
+     * The purpose of this step is to select a project to update dependencies.
+     */
+    static class ProjectStep implements Step<OCIItem, Object> {
+
+        private final CompletableFuture<Project[]> projectsFuture;
+        Project[] projects;
+        private Project selectedProject;
+        private OCIItem item;
+
+        ProjectStep(CompletableFuture<Project[]> projectsFuture) {
+            this.projectsFuture = projectsFuture;
+        }
+
+        @Override
+        public Step<OCIItem, Object> prepare(OCIItem item, Lookup lookup) {
+            this.item = item;
+            try {
+                projects = projectsFuture.get();
+            } catch (InterruptedException ex) {
+                Exceptions.printStackTrace(ex);
+            } catch (ExecutionException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+            return this;
+        }
+
+        @Override
+        public NotifyDescriptor createInput() {
+            List<NotifyDescriptor.QuickPick.Item> items = new 
ArrayList<>(projects.length);
+            for (Project project : projects) {
+                items.add(new NotifyDescriptor.QuickPick.Item(
+                        project.getProjectDirectory().getName(),
+                        project.getProjectDirectory().getName()));

Review Comment:
   Maybe we should use ProjectInfomation here, to display the same project name 
as the user sees in the project explorer (?)



##########
enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/assets/PropertiesGenerator.java:
##########
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.cloud.oracle.assets;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.netbeans.api.db.explorer.ConnectionManager;
+import org.netbeans.api.db.explorer.DatabaseConnection;
+import org.netbeans.modules.cloud.oracle.bucket.BucketItem;
+import org.netbeans.modules.cloud.oracle.items.OCIItem;
+
+/**
+ *
+ * @author Jan Horvath
+ */
+public class PropertiesGenerator {
+
+    private static PropertiesGenerator instance = null;
+
+    private PropertiesGenerator() {
+    }
+
+    public static synchronized PropertiesGenerator getDefault() {
+        if (instance == null) {
+            instance = new PropertiesGenerator();
+        }
+        return instance;
+    }
+
+    public Map<String, String> getProperties(String environment) {
+        Map<String, String> result = new HashMap<>();
+        for (OCIItem item : CloudAssets.getDefault().getItems()) {
+            switch (item.getKey().getPath()) {
+                case "Bucket": //NOI18N
+                    
result.put("micronaut.object-storage.oracle-cloud.default.bucket", 
item.getKey().getValue()); //NOI18N
+                    
result.put("micronaut.object-storage.oracle-cloud.default.namespace", 
((BucketItem) item).getNamespace()); //NOI18N
+                    break;
+                case "Databases": //NOI18N
+                    DatabaseConnection[] connections = 
ConnectionManager.getDefault().getConnections();
+                    DatabaseConnection conn = null;
+                    for (int i = 0; i < connections.length; i++) {
+                        if (item.getKey().getValue().equals(
+                                
connections[i].getConnectionProperties().get("OCID"))) { //NOI18N
+                            conn = connections[i];
+                            break;
+                        }
+                    }
+                    if (conn != null) {

Review Comment:
   What if there's more than one DB with OCID attribute ? There's also some 
`ConnectionManager.getPreferredConnection()` -- wouldn't it be useful to 
attempt disambiguation ?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists


Reply via email to