Skip to content
Snippets Groups Projects
Commit 3ce3bd6d authored by Hallvard Trætteberg's avatar Hallvard Trætteberg
Browse files

Changed to using properties and ConfigAdmin for configuring and managing

dictionaries.
parent 1a4fc096
No related branches found
No related tags found
No related merge requests found
......@@ -14,4 +14,4 @@ A Gogo shell command component is an ordinary Java class annotated as a componen
There are commands for listing dictionaries (all **Dict** service components), looking up words and adding and removing dictionaries, by registering or unregistering **Dict** service components. A custom **Dict** implementation is used for new dictionaries. These may load words from a file and include additional words.
OSGI only allows unregistering components by using the **ServiceRegistration** that was returned when (manually) registering them. Hence, these must be stored by the **add** command and retrieved by the **remove** command. It turned out that the command component isn't a singleton, it is recreated for each invoked command, hence the **ServiceRegistration** must be stored somewhere else. For this a **BundleActivator** is used, i.e. a bundle singleton typically used for storing bundle global data.
OSGI only allows manually unregistering components that have been manually created (for which you have a **ServiceReference** or **Configuration**), so you can only remove dictionaries that have been manually added.
......@@ -15,4 +15,3 @@ javac.source: 1.8
javac.target: 1.8
Bundle-Version: 0.0.0.${tstamp}
\ No newline at end of file
Bundle-Activator: tdt4250.dict3.gogo.Activator
\ No newline at end of file
package tdt4250.dict3.gogo;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import tdt4250.dict3.api.Dict;
public class Activator implements BundleActivator {
private static Activator SINGLETON = null;
static Activator getActivator() {
return SINGLETON;
}
@Override
public void start(BundleContext context) throws Exception {
SINGLETON = this;
}
@Override
public void stop(BundleContext context) throws Exception {
SINGLETON = null;
}
private Map<String, ServiceRegistration<Dict>> serviceRegistrations = new HashMap<String, ServiceRegistration<Dict>>();
public boolean isManual(String dictName) {
return serviceRegistrations.containsKey(dictName);
}
public Collection<String> getAllDictComponentNames() {
Collection<String> allNames = new ArrayList<>();
// iterate through all Dict service objects
BundleContext bc = FrameworkUtil.getBundle(this.getClass()).getBundleContext();
try {
for (ServiceReference<Dict> serviceReference : bc.getServiceReferences(Dict.class, null)) {
Dict dict = bc.getService(serviceReference);
try {
allNames.add(dict.getDictName());
} finally {
bc.ungetService(serviceReference);
}
}
} catch (InvalidSyntaxException e) {
}
return allNames;
}
public boolean addDict(Dict dict) {
boolean existed = removeDict(dict.getDictName());
BundleContext bc = FrameworkUtil.getBundle(this.getClass()).getBundleContext();
ServiceRegistration<Dict> serviceRegistration = bc.registerService(Dict.class, dict, null);
serviceRegistrations.put(dict.getDictName(), serviceRegistration);
return existed;
}
public boolean removeDict(String name) {
if (serviceRegistrations.containsKey(name)) {
serviceRegistrations.get(name).unregister();
serviceRegistrations.remove(name);
return true;
}
return false;
}
}
package tdt4250.dict3.gogo;
import tdt4250.dict3.api.Dict;
import tdt4250.dict3.util.Words;
import tdt4250.dict3.util.WordsDict;
public class CommandDict extends WordsDict implements Dict {
private final String name;
public CommandDict(String name, Words words) {
super(words);
this.name = name;
}
@Override
public String getDictName() {
return name;
}
}
......@@ -3,19 +3,26 @@ package tdt4250.dict3.gogo;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Dictionary;
import java.util.Hashtable;
import org.apache.felix.service.command.Descriptor;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import tdt4250.dict3.api.Dict;
import tdt4250.dict3.api.DictSearchResult;
import tdt4250.dict3.util.MutableWords;
import tdt4250.dict3.util.ResourceWords;
import tdt4250.dict3.util.SortedSetWords;
import tdt4250.dict3.util.WordsDict;
// see https://enroute.osgi.org/FAQ/500-gogo.html
......@@ -31,17 +38,38 @@ import tdt4250.dict3.util.SortedSetWords;
)
public class DictCommands {
private Configuration getConfig(String dictName) {
try {
Configuration[] configs = cm.listConfigurations("(&(" + WordsDict.DICT_NAME_PROP + "=" + dictName + ")(service.factoryPid=" + WordsDict.FACTORY_PID + "))");
if (configs != null && configs.length >= 1) {
return configs[0];
}
} catch (IOException | InvalidSyntaxException e) {
}
return null;
}
@Descriptor("list available dictionaries")
public void list() {
Activator activator = Activator.getActivator();
System.out.print("Dictionaries: ");
for (String dictName : activator.getAllDictComponentNames()) {
System.out.print(dictName);
if (activator.isManual(dictName)) {
BundleContext bc = FrameworkUtil.getBundle(this.getClass()).getBundleContext();
try {
for (ServiceReference<Dict> serviceReference : bc.getServiceReferences(Dict.class, null)) {
Dict dict = bc.getService(serviceReference);
try {
if (dict != null) {
System.out.print(dict.getDictName());
if (getConfig(dict.getDictName()) != null) {
System.out.print("*");
}
}
} finally {
bc.ungetService(serviceReference);
}
System.out.print(" ");
}
} catch (InvalidSyntaxException e) {
}
System.out.println();
}
......@@ -55,17 +83,24 @@ public class DictCommands {
// iterate through all Dict service objects
for (ServiceReference<Dict> serviceReference : bc.getServiceReferences(Dict.class, null)) {
Dict dict = bc.getService(serviceReference);
if (dict != null) {
try {
DictSearchResult search = dict.search(s);
System.out.println(dict.getDictName() + ": " + search.getMessage());
} finally {
bc.ungetService(serviceReference);
}
} else {
System.out.println(serviceReference.getProperties());
}
}
} catch (InvalidSyntaxException e) {
}
}
@Reference(cardinality = ReferenceCardinality.MANDATORY)
private ConfigurationAdmin cm;
@Descriptor("add a dictionary, with content from a URL and/or specific words")
public void add(
@Descriptor("the name of the new dictionary")
......@@ -74,33 +109,46 @@ public class DictCommands {
String urlStringOrWord,
@Descriptor("additional words to add to the dictionary")
String... ss
) {
MutableWords words = null;
) throws IOException, InvalidSyntaxException {
URL url = null;
Collection<String> words = new ArrayList<String>();
try {
URL url = new URL(urlStringOrWord);
try {
words = new ResourceWords(url.openStream());
} catch (IOException e) {
System.err.println("Couldn't read from " + url);
return;
}
url = new URL(urlStringOrWord);
} catch (MalformedURLException e) {
words = new SortedSetWords();
words.addWord(urlStringOrWord);
words.add(urlStringOrWord);
}
words.addAll(Arrays.asList(ss));
String actionName = "updated";
// lookup existing configuration
Configuration config = getConfig(name);
if (config == null) {
// create a new one
config = cm.createFactoryConfiguration(WordsDict.FACTORY_PID, "?");
actionName = "added";
}
for (String s : ss) {
words.addWord(s);
Dictionary<String, String> props = new Hashtable<>();
props.put(WordsDict.DICT_NAME_PROP, name);
if (url != null) {
props.put(WordsDict.DICT_RESOURCE_PROP, url.toString());
}
boolean existed = Activator.getActivator().addDict(new CommandDict(name, words));
System.out.println("\"" + name + "\" dictionary " + (existed ? "replaced" : "added"));
if (words != null && words.size() > 0) {
props.put(WordsDict.DICT_WORDS_PROP, String.join(" ", words));
}
config.update(props);
System.out.println("\"" + name + "\" dictionary " + actionName);
}
@Descriptor("remove a (manually added) dictionary")
public void remove(
@Descriptor("the name of the (manually added) dictionary to remove")
String name
) {
boolean removed = Activator.getActivator().removeDict(name);
) throws IOException, InvalidSyntaxException {
Configuration config = getConfig(name);
boolean removed = false;
if (config != null) {
config.delete();
removed = true;
}
System.out.println("\"" + name + "\" dictionary " + (removed ? "removed" : "was not added manually"));
}
}
package tdt4250.dict3.no;
import java.io.IOException;
import org.osgi.service.component.annotations.Component;
import tdt4250.dict3.api.Dict;
import tdt4250.dict3.util.WordsDict;
@Component
@Component(
property = {
WordsDict.DICT_NAME_PROP + "=nb",
WordsDict.DICT_RESOURCE_PROP + "=tdt4250.dict3.no#/tdt4250/dict3/no/nb.txt"}
)
public class NbDict extends WordsDict implements Dict {
public NbDict() throws IOException {
super(NbDict.class.getResourceAsStream("nb.txt"));
}
@Override
public String getDictName() {
return "nb";
}
}
......@@ -5,8 +5,9 @@
-resolve.effective: active
-runproperties: \
org.osgi.service.http.port=8080,\
osgi.clean=true,\
osgi.console=,\
org.osgi.service.http.port=8080,\
osgi.console.enable.builtin=false
-runrequires: \
......@@ -14,7 +15,9 @@
osgi.identity;filter:='(osgi.identity=org.apache.felix.gogo.command)',\
bnd.identity;id='tdt4250.dict3.servlet',\
bnd.identity;id='tdt4250.dict3.no',\
bnd.identity;id='tdt4250.dict3.gogo'
bnd.identity;id='tdt4250.dict3.gogo',\
bnd.identity;id='org.apache.felix.configadmin',\
bnd.identity;id='org.osgi.service.cm'
-runbundles: \
org.apache.felix.gogo.command;version='[1.0.2,1.0.3)',\
org.apache.felix.gogo.runtime;version='[1.0.10,1.0.11)',\
......@@ -26,4 +29,6 @@
tdt4250.dict3.servlet;version=snapshot,\
tdt4250.dict3.no;version=snapshot,\
tdt4250.dict3.util;version=snapshot,\
tdt4250.dict3.gogo;version=snapshot
\ No newline at end of file
tdt4250.dict3.gogo;version=snapshot,\
org.apache.felix.configadmin;version='[1.9.8,1.9.9)',\
org.osgi.service.cm;version='[1.5.0,1.5.1)'
\ No newline at end of file
# tdt4250.dict3.no bundle
This bundle is part of variant 3 of the [tdt4250.dict project](../README.md), and was created using the API template. It contains utility classes useful for dictionary implementations.
This bundle is part of variant 3 of the [tdt4250.dict project](../README.md), and was created using the API template. It contains utility classes useful for dictionary implementations, as well as a **Dict** implementation that can be configured using the **ConfigAdmin** OSGi service.
## Packages
- **tdt4250.dict3.no**: Utility classes useful for dictionary implementations.
## Design
The main class of this bundle is the **WordsDict** implementation of the **Dict** service. Both the name and the words can be configured using properties, and the words may be both loaded from a **URL** (or as a special case a resource in a bundle) or from a property. Thus, by creating a **Configuration** with the appropriate properties, a new **Dict** may be created and automatically injected into **Dict** service consumers.
The **configurationPid** is **tdt4250.dict3.util.WordsDict**, i.e. the class name, and is used as the **factoryPid** when creating new **Configuration** objects.
The properties are:
- **dictName**: the name of the new dictionary
- **dictResource**: the URL or location of a bundle resource (format <bundle>#<resource-path>, e.g. tdt4250.dict3.no#/tdt4250/dict3/no/nb.txt)that contains the words
- **dictWords**: a space-separated list of the words
Constants for these are defined in the **WordsDict** class.
......@@ -3,7 +3,10 @@ Bundle-Version: 1.0.0.${tstamp}
-baseline: *
-buildpath: \
osgi.annotation;version=7.0.0,\
tdt4250.dict3.api;version=latest
tdt4250.dict3.api;version=latest,\
osgi.cmpn,\
org.osgi.service.cm,\
org.osgi.core
javac.source: 1.8
javac.target: 1.8
......
......@@ -2,19 +2,107 @@ package tdt4250.dict3.util;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ConfigurationPolicy;
import org.osgi.service.component.annotations.Modified;
import tdt4250.dict3.api.Dict;
import tdt4250.dict3.api.DictSearchResult;
public abstract class WordsDict {
@Component(
configurationPid = WordsDict.FACTORY_PID,
configurationPolicy = ConfigurationPolicy.REQUIRE
)
public class WordsDict implements Dict {
public static final String FACTORY_PID = "tdt4250.dict3.util.WordsDict";
public static final String DICT_WORDS_PROP = "dictWords";
public static final String DICT_RESOURCE_PROP = "dictResource";
public static final String DICT_NAME_PROP = "dictName";
private String name;
private Words words;
protected WordsDict(Words words) {
@Override
public String getDictName() {
return name;
}
protected void setDictName(String name) {
this.name = name;
}
public @interface WordsDictConfig {
String dictName();
String dictResource() default "";
String[] dictWords() default {};
}
@Activate
public void activate(BundleContext bc, WordsDictConfig config) {
update(bc, config);
}
@Modified
public void modify(BundleContext bc, WordsDictConfig config) {
update(bc, config);
}
protected void update(BundleContext bc, WordsDictConfig config) {
setDictName(config.dictName());
String dictUrl = config.dictResource();
if (dictUrl.length() > 0) {
URL url = null;
try {
url = new URL(dictUrl);
} catch (MalformedURLException e) {
// try bundle resource format: <bundle-id>#<resource-path>
int pos = dictUrl.indexOf('#');
String bundleId = dictUrl.substring(0, pos);
String resourcePath = dictUrl.substring(pos + 1);
for (Bundle bundle : bc.getBundles()) {
if (bundle.getSymbolicName().equals(bundleId)) {
url = bundle.getResource(resourcePath);
}
}
}
try {
System.out.println("Loading words from " + url);
words = new ResourceWords(url.openStream());
} catch (IOException e) {
System.err.println(e);
}
}
if (config.dictWords().length > 0) {
String[] ss = config.dictWords();
if (words == null) {
words = new SortedSetWords();
}
if (words instanceof MutableWords) {
for (int i = 0; i < ss.length; i++) {
((MutableWords) words).addWord(ss[i].trim());
}
}
}
}
protected void setWords(Words words) {
this.words = words;
}
protected WordsDict(InputStream input) throws IOException {
this.words = new ResourceWords(input);
protected void setWords(InputStream input) throws IOException {
words = new ResourceWords(input);
}
protected void setWords(URL url) throws IOException {
setWords(url.openStream());
}
protected String getSuccessMessageStringFormat() {
......@@ -26,7 +114,7 @@ public abstract class WordsDict {
}
public DictSearchResult search(String searchKey) {
if (words.hasWord(searchKey)) {
if (words != null && words.hasWord(searchKey)) {
return new DictSearchResult(true, String.format(getSuccessMessageStringFormat(), searchKey), null);
} else {
return new DictSearchResult(false, String.format(getFailureMessageStringFormat(), searchKey), null);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment