diff --git a/dict-ws/tdt4250.dict3.gogo/README.md b/dict-ws/tdt4250.dict3.gogo/README.md index 009cae1ea873eb36343eba2a13e2a4176e8d30dc..fa5d9c7025ce9c88ffdc5b1b7a09d5e923a84d9c 100644 --- a/dict-ws/tdt4250.dict3.gogo/README.md +++ b/dict-ws/tdt4250.dict3.gogo/README.md @@ -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. diff --git a/dict-ws/tdt4250.dict3.gogo/bnd.bnd b/dict-ws/tdt4250.dict3.gogo/bnd.bnd index c8278b1995232f505e06d4d24f1839cbf6ceb493..8fff410d72193da6fe94ded8e13a08b64091fbbc 100644 --- a/dict-ws/tdt4250.dict3.gogo/bnd.bnd +++ b/dict-ws/tdt4250.dict3.gogo/bnd.bnd @@ -14,5 +14,4 @@ javac.source: 1.8 javac.target: 1.8 -Bundle-Version: 0.0.0.${tstamp} -Bundle-Activator: tdt4250.dict3.gogo.Activator \ No newline at end of file +Bundle-Version: 0.0.0.${tstamp} \ No newline at end of file diff --git a/dict-ws/tdt4250.dict3.gogo/src/tdt4250/dict3/gogo/Activator.java b/dict-ws/tdt4250.dict3.gogo/src/tdt4250/dict3/gogo/Activator.java deleted file mode 100644 index 6ccb5c7fe007492756a09bd5ef273aa6c4f4ab5f..0000000000000000000000000000000000000000 --- a/dict-ws/tdt4250.dict3.gogo/src/tdt4250/dict3/gogo/Activator.java +++ /dev/null @@ -1,75 +0,0 @@ -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; - } -} diff --git a/dict-ws/tdt4250.dict3.gogo/src/tdt4250/dict3/gogo/CommandDict.java b/dict-ws/tdt4250.dict3.gogo/src/tdt4250/dict3/gogo/CommandDict.java deleted file mode 100644 index f8781d326be42ef681467d6c374d4720ebfabda6..0000000000000000000000000000000000000000 --- a/dict-ws/tdt4250.dict3.gogo/src/tdt4250/dict3/gogo/CommandDict.java +++ /dev/null @@ -1,21 +0,0 @@ -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; - } - -} diff --git a/dict-ws/tdt4250.dict3.gogo/src/tdt4250/dict3/gogo/DictCommands.java b/dict-ws/tdt4250.dict3.gogo/src/tdt4250/dict3/gogo/DictCommands.java index 6aaf2627028f50baeecee7aa90771431c07aef3f..dff6e1cee7659ff325fedbfffac333e33366561c 100644 --- a/dict-ws/tdt4250.dict3.gogo/src/tdt4250/dict3/gogo/DictCommands.java +++ b/dict-ws/tdt4250.dict3.gogo/src/tdt4250/dict3/gogo/DictCommands.java @@ -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,16 +38,37 @@ 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)) { - System.out.print("*"); + 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(" "); } - 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); - try { - DictSearchResult search = dict.search(s); - System.out.println(dict.getDictName() + ": " + search.getMessage()); - } finally { - bc.ungetService(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"; + } + Dictionary<String, String> props = new Hashtable<>(); + props.put(WordsDict.DICT_NAME_PROP, name); + if (url != null) { + props.put(WordsDict.DICT_RESOURCE_PROP, url.toString()); } - for (String s : ss) { - words.addWord(s); + if (words != null && words.size() > 0) { + props.put(WordsDict.DICT_WORDS_PROP, String.join(" ", words)); } - boolean existed = Activator.getActivator().addDict(new CommandDict(name, words)); - System.out.println("\"" + name + "\" dictionary " + (existed ? "replaced" : "added")); + 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")); } } diff --git a/dict-ws/tdt4250.dict3.no/src/tdt4250/dict3/no/NbDict.java b/dict-ws/tdt4250.dict3.no/src/tdt4250/dict3/no/NbDict.java index 3cf2f0784f0342b0dfe21f804e4aa740219f964d..c9de058ee480b6397143b99e82c0870f16f588cb 100644 --- a/dict-ws/tdt4250.dict3.no/src/tdt4250/dict3/no/NbDict.java +++ b/dict-ws/tdt4250.dict3.no/src/tdt4250/dict3/no/NbDict.java @@ -1,21 +1,14 @@ 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"; - } } diff --git a/dict-ws/tdt4250.dict3.servlet/launch.bndrun b/dict-ws/tdt4250.dict3.servlet/launch.bndrun index c5e51e570982e87a8bb81b2a9363f98c6368c356..6cb822ace66519a43f4828ac0f77cc4e10b430fa 100644 --- a/dict-ws/tdt4250.dict3.servlet/launch.bndrun +++ b/dict-ws/tdt4250.dict3.servlet/launch.bndrun @@ -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 diff --git a/dict-ws/tdt4250.dict3.util/README.md b/dict-ws/tdt4250.dict3.util/README.md index 45a10a02d4d1eec0afefbd997f95d7d2f879bae5..8fb12d4f21cd6bba467f684441ba990c7170df07 100644 --- a/dict-ws/tdt4250.dict3.util/README.md +++ b/dict-ws/tdt4250.dict3.util/README.md @@ -1,7 +1,21 @@ # 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. diff --git a/dict-ws/tdt4250.dict3.util/bnd.bnd b/dict-ws/tdt4250.dict3.util/bnd.bnd index 7849058f5fec812967dc004ef77c59ad250fdd41..80a80c0c641d32dcd92b96291b25aa26b566e705 100644 --- a/dict-ws/tdt4250.dict3.util/bnd.bnd +++ b/dict-ws/tdt4250.dict3.util/bnd.bnd @@ -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 diff --git a/dict-ws/tdt4250.dict3.util/src/tdt4250/dict3/util/WordsDict.java b/dict-ws/tdt4250.dict3.util/src/tdt4250/dict3/util/WordsDict.java index aa5e738024880c3a58566138d4dadd6ad9af03bd..61a00222e48d92ed1866fa7bb3ed871351c8983e 100644 --- a/dict-ws/tdt4250.dict3.util/src/tdt4250/dict3/util/WordsDict.java +++ b/dict-ws/tdt4250.dict3.util/src/tdt4250/dict3/util/WordsDict.java @@ -2,21 +2,109 @@ 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) { - this.words = words; + @Override + public String getDictName() { + return name; } - protected WordsDict(InputStream input) throws IOException { - this.words = new ResourceWords(input); + 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 void setWords(InputStream input) throws IOException { + words = new ResourceWords(input); + } + + protected void setWords(URL url) throws IOException { + setWords(url.openStream()); + } + protected String getSuccessMessageStringFormat() { return "Yes, %s was found!"; } @@ -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);