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