001package de.deepamehta.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);
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    // --- not part of OSGi service ---
111
112    @GET
113    public ConfigDefinitions getConfigDefinitions() {
114        return new ConfigDefinitions(registry);
115    }
116
117
118
119    // ********************************
120    // *** Listener Implementations ***
121    // ********************************
122
123
124
125    @Override
126    public void postCreateTopic(Topic topic) {
127        for (ConfigDefinition configDef : getApplicableConfigDefinitions(topic)) {
128            createConfigTopic(configDef, topic);
129        }
130    }
131
132
133
134    // ------------------------------------------------------------------------------------------------- Private Methods
135
136    private RelatedTopic _getConfigTopic(String configTypeUri, long topicId) {
137        return dm4.getAccessControl().getConfigTopic(configTypeUri, topicId);
138    }
139
140    private RelatedTopic createConfigTopic(final ConfigDefinition configDef, final Topic topic) {
141        final String configTypeUri = configDef.getConfigTypeUri();
142        try {
143            logger.info("### Creating config topic of type \"" + configTypeUri + "\" for topic " + topic.getId());
144            // We suppress standard workspace assignment here as a config topic requires a special assignment.
145            // See assignConfigTopicToWorkspace() below.
146            return dm4.getAccessControl().runWithoutWorkspaceAssignment(new Callable<RelatedTopic>() {
147                @Override
148                public RelatedTopic call() {
149                    Topic configTopic = dm4.createTopic(configDef.getConfigValue(topic));
150                    dm4.createAssociation(mf.newAssociationModel(ASSOC_TYPE_CONFIGURATION,
151                        mf.newTopicRoleModel(topic.getId(), ROLE_TYPE_CONFIGURABLE),
152                        mf.newTopicRoleModel(configTopic.getId(), ROLE_TYPE_DEFAULT)));
153                    assignConfigTopicToWorkspace(configTopic, configDef.getConfigModificationRole());
154                    // ### TODO: extend Core API to avoid re-retrieval
155                    return _getConfigTopic(configTypeUri, topic.getId());
156                }
157            });
158        } catch (Exception e) {
159            throw new RuntimeException("Creating config topic of type \"" + configTypeUri + "\" for topic " +
160                topic.getId() + " failed", e);
161        }
162    }
163
164    private void assignConfigTopicToWorkspace(Topic configTopic, ConfigModificationRole role) {
165        long workspaceId;
166        AccessControl ac = dm4.getAccessControl();
167        switch (role) {
168        case ADMIN:
169            workspaceId = ac.getAdministrationWorkspaceId();
170            break;
171        case SYSTEM:
172            workspaceId = ac.getSystemWorkspaceId();
173            break;
174        default:
175            throw new RuntimeException("Modification role \"" + role + "\" not yet implemented");
176        }
177        ac.assignToWorkspace(configTopic, workspaceId);
178    }
179
180    // ---
181
182    /**
183     * Returns all configuration definitions applicable to a given topic.
184     *
185     * @return  a list of configuration definitions, possibly empty.
186     */
187    private List<ConfigDefinition> getApplicableConfigDefinitions(Topic topic) {
188        List<ConfigDefinition> configDefs1 = lookupConfigDefinitions(ConfigTarget.SINGLETON.hashKey(topic));
189        List<ConfigDefinition> configDefs2 = lookupConfigDefinitions(ConfigTarget.TYPE_INSTANCES.hashKey(topic));
190        if (configDefs1 != null && configDefs2 != null) {
191            List<ConfigDefinition> configDefs = new ArrayList();
192            configDefs.addAll(configDefs1);
193            configDefs.addAll(configDefs2);
194            return configDefs;
195        }
196        return configDefs1 != null ? configDefs1 : configDefs2 != null ? configDefs2 : new ArrayList();
197    }
198
199    /**
200     * Returns the configuration definition for the given config type that is applicable to the given topic.
201     *
202     * @throws RuntimeException     if no such configuration definition is registered.
203     */
204    private ConfigDefinition getApplicableConfigDefinition(Topic topic, String configTypeUri) {
205        List<ConfigDefinition> configDefs = getApplicableConfigDefinitions(topic);
206        if (configDefs.size() == 0) {
207            throw new RuntimeException("None of the registered configuration definitions are applicable to " +
208                info(topic));
209        }
210        ConfigDefinition configDef = findByConfigTypeUri(configDefs, configTypeUri);
211        if (configDef == null) {
212            throw new RuntimeException("For " + info(topic) + " no configuration definition for type \"" +
213                configTypeUri + "\" registered");
214        }
215        return configDef;
216    }
217
218    // ---
219
220    private boolean isRegistered(ConfigDefinition configDef) {
221        for (List<ConfigDefinition> configDefs : registry.values()) {
222            if (configDefs.contains(configDef)) {
223                return true;
224            }
225        }
226        return false;
227    }
228
229    private ConfigDefinition findByConfigTypeUri(List<ConfigDefinition> configDefs, String configTypeUri) {
230        for (ConfigDefinition configDef : configDefs) {
231            if (configDef.getConfigTypeUri().equals(configTypeUri)) {
232                return configDef;
233            }
234        }
235        return null;
236    }
237
238    private List<ConfigDefinition> lookupConfigDefinitions(String hashKey) {
239        return registry.get(hashKey);
240    }
241
242    // ---
243
244    private String info(Topic topic) {
245        return "topic " + topic.getId() + " (value=\"" + topic.getSimpleValue() + "\", typeUri=\"" +
246            topic.getTypeUri() + "\", uri=\"" + topic.getUri() + "\")";
247    }
248}