001package de.deepamehta.plugins.config;
002
003import de.deepamehta.core.RelatedTopic;
004import de.deepamehta.core.Topic;
005import de.deepamehta.core.model.AssociationModel;
006import de.deepamehta.core.model.SimpleValue;
007import de.deepamehta.core.model.TopicRoleModel;
008import de.deepamehta.core.osgi.PluginActivator;
009import de.deepamehta.core.service.Transactional;
010import de.deepamehta.core.service.accesscontrol.AccessControl;
011import de.deepamehta.core.service.event.PostCreateTopicListener;
012
013import javax.ws.rs.GET;
014import javax.ws.rs.Path;
015import javax.ws.rs.PathParam;
016import javax.ws.rs.Produces;
017
018import java.util.ArrayList;
019import java.util.HashMap;
020import java.util.List;
021import java.util.Map;
022import java.util.concurrent.Callable;
023import java.util.logging.Logger;
024
025
026
027@Path("/config")
028@Produces("application/json")
029public class ConfigPlugin extends PluginActivator implements ConfigService, PostCreateTopicListener {
030
031    // ------------------------------------------------------------------------------------------------------- Constants
032
033    private static String ASSOC_TYPE_CONFIGURATION = "dm4.config.configuration";
034    private static String ROLE_TYPE_CONFIGURABLE = "dm4.config.configurable";
035    private static String ROLE_TYPE_DEFAULT = "dm4.core.default";
036
037    // ---------------------------------------------------------------------------------------------- Instance Variables
038
039    /**
040     * Key: the "configurable URI" as a config target's hash key, that is either "topic_uri:{uri}" or "type_uri:{uri}".
041     */
042    private Map<String, List<ConfigDefinition>> registry = new HashMap();
043
044    private Logger logger = Logger.getLogger(getClass().getName());
045
046    // -------------------------------------------------------------------------------------------------- Public Methods
047
048
049
050    // ************************************
051    // *** ConfigService Implementation ***
052    // ************************************
053
054
055
056    @GET
057    @Path("/{config_type_uri}/topic/{topic_id}")
058    @Override    
059    public RelatedTopic getConfigTopic(@PathParam("config_type_uri") String configTypeUri,
060                                       @PathParam("topic_id") long topicId) {
061        return _getConfigTopic(configTypeUri, topicId);
062    }
063
064    @Override
065    public void createConfigTopic(String configTypeUri, Topic topic) {
066        createConfigTopic(getApplicableConfigDefinition(topic, configTypeUri), topic.getId());
067    }
068
069    // ---
070
071    @Override
072    public void registerConfigDefinition(ConfigDefinition configDef) {
073        try {
074            if (isRegistered(configDef)) {
075                throw new RuntimeException("A definition for configuration type \"" + configDef.getConfigTypeUri() +
076                    "\" is already registered");
077            }
078            //
079            String hashKey = configDef.getHashKey();
080            List<ConfigDefinition> configDefs = lookupConfigDefinitions(hashKey);
081            if (configDefs == null) {
082                configDefs = new ArrayList();
083                registry.put(hashKey, configDefs);
084            }
085            configDefs.add(configDef);
086        } catch (Exception e) {
087            throw new RuntimeException("Registering a configuration definition failed", e);
088        }
089    }
090
091    @Override
092    public void unregisterConfigDefinition(String configTypeUri) {
093        try {
094            for (List<ConfigDefinition> configDefs : registry.values()) {
095                ConfigDefinition configDef = findByConfigTypeUri(configDefs, configTypeUri);
096                if (configDef != null) {
097                    if (!configDefs.remove(configDef)) {
098                        throw new RuntimeException("Configuration definition could not be removed from registry");
099                    }
100                    return;
101                }
102            }
103            throw new RuntimeException("No such configuration definition registered");
104        } catch (Exception e) {
105            throw new RuntimeException("Unregistering definition for configuration type \"" + configTypeUri +
106                "\" failed", e);
107        }
108    }
109
110    // ---
111
112    @GET
113    @Override    
114    public ConfigDefinitions getConfigDefinitions() {
115        return new ConfigDefinitions(registry);
116    }
117
118
119
120    // ********************************
121    // *** Listener Implementations ***
122    // ********************************
123
124
125
126    @Override
127    public void postCreateTopic(Topic topic) {
128        for (ConfigDefinition configDef : getApplicableConfigDefinitions(topic)) {
129            createConfigTopic(configDef, topic.getId());
130        }
131    }
132
133
134
135    // ------------------------------------------------------------------------------------------------- Private Methods
136
137    private RelatedTopic _getConfigTopic(String configTypeUri, long topicId) {
138        return dms.getAccessControl().getConfigTopic(configTypeUri, topicId);
139    }
140
141    private RelatedTopic createConfigTopic(final ConfigDefinition configDef, final long topicId) {
142        final String configTypeUri = configDef.getConfigTypeUri();
143        try {
144            logger.info("### Creating config topic of type \"" + configTypeUri + "\" for topic " + topicId);
145            // We suppress standard workspace assignment here as a config topic requires a special assignment.
146            // See assignConfigTopicToWorkspace() below.
147            return dms.getAccessControl().runWithoutWorkspaceAssignment(new Callable<RelatedTopic>() {
148                @Override
149                public RelatedTopic call() {
150                    Topic configTopic = dms.createTopic(configDef.getDefaultConfigTopic());
151                    dms.createAssociation(new AssociationModel(ASSOC_TYPE_CONFIGURATION,
152                        new TopicRoleModel(topicId, ROLE_TYPE_CONFIGURABLE),
153                        new TopicRoleModel(configTopic.getId(), ROLE_TYPE_DEFAULT)));
154                    assignConfigTopicToWorkspace(configTopic, configDef.getConfigModificationRole());
155                    // ### TODO: extend Core API to avoid re-retrieval
156                    return _getConfigTopic(configTypeUri, topicId);
157                }
158            });
159        } catch (Exception e) {
160            throw new RuntimeException("Creating config topic of type \"" + configTypeUri + "\" for topic " +
161                topicId + " failed", e);
162        }
163    }
164
165    private void assignConfigTopicToWorkspace(Topic configTopic, ConfigModificationRole role) {
166        long workspaceId;
167        AccessControl ac = dms.getAccessControl();
168        switch (role) {
169        case ADMIN:
170            workspaceId = ac.getSystemWorkspaceId();
171            break;
172        default:
173            throw new RuntimeException("Modification role \"" + role + "\" not yet implemented");
174        }
175        ac.assignToWorkspace(configTopic, workspaceId);
176    }
177
178    // ---
179
180    /**
181     * Returns all configuration definitions applicable to a given topic.
182     *
183     * @return  a list of configuration definitions, possibly empty.
184     */
185    private List<ConfigDefinition> getApplicableConfigDefinitions(Topic topic) {
186        List<ConfigDefinition> configDefs1 = lookupConfigDefinitions(ConfigTarget.SINGLETON.hashKey(topic));
187        List<ConfigDefinition> configDefs2 = lookupConfigDefinitions(ConfigTarget.TYPE_INSTANCES.hashKey(topic));
188        if (configDefs1 != null && configDefs2 != null) {
189            List<ConfigDefinition> configDefs = new ArrayList();
190            configDefs.addAll(configDefs1);
191            configDefs.addAll(configDefs2);
192            return configDefs;
193        }
194        return configDefs1 != null ? configDefs1 : configDefs2 != null ? configDefs2 : new ArrayList();
195    }
196
197    /**
198     * Returns the configuration definition for the given config type that is applicable to the given topic.
199     *
200     * @throws RuntimeException     if no such configuration definition is registered.
201     */
202    private ConfigDefinition getApplicableConfigDefinition(Topic topic, String configTypeUri) {
203        List<ConfigDefinition> configDefs = getApplicableConfigDefinitions(topic);
204        if (configDefs.size() == 0) {
205            throw new RuntimeException("None of the registered configuration definitions are applicable to " +
206                info(topic));
207        }
208        ConfigDefinition configDef = findByConfigTypeUri(configDefs, configTypeUri);
209        if (configDef == null) {
210            throw new RuntimeException("For " + info(topic) + " no configuration definition for type \"" +
211                configTypeUri + "\" registered");
212        }
213        return configDef;
214    }
215
216    // ---
217
218    private boolean isRegistered(ConfigDefinition configDef) {
219        for (List<ConfigDefinition> configDefs : registry.values()) {
220            if (configDefs.contains(configDef)) {
221                return true;
222            }
223        }
224        return false;
225    }
226
227    private ConfigDefinition findByConfigTypeUri(List<ConfigDefinition> configDefs, String configTypeUri) {
228        for (ConfigDefinition configDef : configDefs) {
229            if (configDef.getConfigTypeUri().equals(configTypeUri)) {
230                return configDef;
231            }
232        }
233        return null;
234    }
235
236    private List<ConfigDefinition> lookupConfigDefinitions(String hashKey) {
237        return registry.get(hashKey);
238    }
239
240    // ---
241
242    private String info(Topic topic) {
243        return "topic " + topic.getId() + " (value=\"" + topic.getSimpleValue() + "\", typeUri=\"" +
244            topic.getTypeUri() + "\", uri=\"" + topic.getUri() + "\")";
245    }
246}