001package systems.dmx.config;
002
003import systems.dmx.core.RelatedTopic;
004import systems.dmx.core.Topic;
005import systems.dmx.core.model.AssociationModel;
006import systems.dmx.core.model.SimpleValue;
007import systems.dmx.core.model.TopicRoleModel;
008import systems.dmx.core.osgi.PluginActivator;
009import systems.dmx.core.service.Transactional;
010import systems.dmx.core.service.accesscontrol.AccessControl;
011import systems.dmx.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 = "dmx.config.configuration";
040    private static String ROLE_TYPE_CONFIGURABLE = "dmx.config.configurable";
041    private static String ROLE_TYPE_DEFAULT = "dmx.core.default";
042
043    // ---------------------------------------------------------------------------------------------- Instance Variables
044
045    /**
046     * Key: the "configurable URI" as a config target's hash key, that is either "topicUri:{uri}" or "typeUri:{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 config 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 config 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("Config definition could not be removed from registry");
108                    }
109                    return;
110                }
111            }
112            throw new RuntimeException("No such config definition registered");
113        } catch (Exception e) {
114            throw new RuntimeException("Unregistering definition for config type \"" + configTypeUri + "\" failed", e);
115        }
116    }
117
118    // --- not part of OSGi service ---
119
120    @GET
121    public ConfigDefinitions getConfigDefinitions() {
122        try {
123            JSONObject json = new JSONObject();
124            AccessControl ac = dmx.getAccessControl();
125            for (String configurableUri: registry.keySet()) {
126                JSONArray array = new JSONArray();
127                for (ConfigDefinition configDef : lookupConfigDefinitions(configurableUri)) {
128                    String username = ac.getUsername(request);
129                    long workspaceId = workspaceId(configDef.getConfigModificationRole());
130                    if (ac.hasReadPermission(username, workspaceId)) {
131                        array.put(configDef.getConfigTypeUri());
132                    }
133                }
134                json.put(configurableUri, array);
135            }
136            return new ConfigDefinitions(json);
137        } catch (Exception e) {
138            throw new RuntimeException("Retrieving the registered config definitions failed", e);
139        }
140    }
141
142
143
144    // ********************************
145    // *** Listener Implementations ***
146    // ********************************
147
148
149
150    @Override
151    public void postCreateTopic(Topic topic) {
152        for (ConfigDefinition configDef : getApplicableConfigDefinitions(topic)) {
153            _createConfigTopic(configDef, topic);
154        }
155    }
156
157
158
159    // ------------------------------------------------------------------------------------------------- Private Methods
160
161    private RelatedTopic _getConfigTopic(String configTypeUri, long topicId) {
162        return dmx.getAccessControl().getConfigTopic(configTypeUri, topicId);
163    }
164
165    private RelatedTopic _createConfigTopic(final ConfigDefinition configDef, final Topic topic) {
166        final String configTypeUri = configDef.getConfigTypeUri();
167        try {
168            logger.info("### Creating config topic of type \"" + configTypeUri + "\" for topic " + topic.getId());
169            // suppress standard workspace assignment as a config topic requires a special assignment
170            final AccessControl ac = dmx.getAccessControl();
171            return ac.runWithoutWorkspaceAssignment(new Callable<RelatedTopic>() {
172                @Override
173                public RelatedTopic call() {
174                    Topic configTopic = dmx.createTopic(configDef.getConfigValue(topic));
175                    dmx.createAssociation(mf.newAssociationModel(ASSOC_TYPE_CONFIGURATION,
176                        mf.newTopicRoleModel(topic.getId(), ROLE_TYPE_CONFIGURABLE),
177                        mf.newTopicRoleModel(configTopic.getId(), ROLE_TYPE_DEFAULT)));
178                    ac.assignToWorkspace(configTopic, workspaceId(configDef.getConfigModificationRole()));
179                    // ### TODO: extend Core API to avoid re-retrieval
180                    return _getConfigTopic(configTypeUri, topic.getId());
181                }
182            });
183        } catch (Exception e) {
184            throw new RuntimeException("Creating config topic of type \"" + configTypeUri + "\" for topic " +
185                topic.getId() + " failed", e);
186        }
187    }
188
189    private long workspaceId(ConfigModificationRole role) {
190        AccessControl ac = dmx.getAccessControl();
191        switch (role) {
192        case ADMIN:
193            return ac.getAdministrationWorkspaceId();
194        case SYSTEM:
195            return ac.getSystemWorkspaceId();
196        default:
197            throw new RuntimeException("Modification role \"" + role + "\" not yet implemented");
198        }
199    }
200
201    // ---
202
203    /**
204     * Returns all config definitions applicable to a given topic.
205     *
206     * @return  a list of config definitions, possibly empty.
207     */
208    private List<ConfigDefinition> getApplicableConfigDefinitions(Topic topic) {
209        List<ConfigDefinition> configDefs1 = lookupConfigDefinitions(ConfigTarget.SINGLETON.hashKey(topic));
210        List<ConfigDefinition> configDefs2 = lookupConfigDefinitions(ConfigTarget.TYPE_INSTANCES.hashKey(topic));
211        if (configDefs1 != null && configDefs2 != null) {
212            List<ConfigDefinition> configDefs = new ArrayList();
213            configDefs.addAll(configDefs1);
214            configDefs.addAll(configDefs2);
215            return configDefs;
216        }
217        return configDefs1 != null ? configDefs1 : configDefs2 != null ? configDefs2 : new ArrayList();
218    }
219
220    /**
221     * Returns the config definition for the given config type that is applicable to the given topic.
222     *
223     * @throws RuntimeException     if no such config definition is registered.
224     */
225    private ConfigDefinition getApplicableConfigDefinition(Topic topic, String configTypeUri) {
226        List<ConfigDefinition> configDefs = getApplicableConfigDefinitions(topic);
227        if (configDefs.size() == 0) {
228            throw new RuntimeException("None of the registered config definitions are applicable to " + info(topic));
229        }
230        ConfigDefinition configDef = findByConfigTypeUri(configDefs, configTypeUri);
231        if (configDef == null) {
232            throw new RuntimeException("For " + info(topic) + " no config definition for type \"" + configTypeUri +
233                "\" registered");
234        }
235        return configDef;
236    }
237
238    // ---
239
240    private boolean isRegistered(ConfigDefinition configDef) {
241        for (List<ConfigDefinition> configDefs : registry.values()) {
242            if (configDefs.contains(configDef)) {
243                return true;
244            }
245        }
246        return false;
247    }
248
249    private ConfigDefinition findByConfigTypeUri(List<ConfigDefinition> configDefs, String configTypeUri) {
250        for (ConfigDefinition configDef : configDefs) {
251            if (configDef.getConfigTypeUri().equals(configTypeUri)) {
252                return configDef;
253            }
254        }
255        return null;
256    }
257
258    private List<ConfigDefinition> lookupConfigDefinitions(String hashKey) {
259        return registry.get(hashKey);
260    }
261
262    // ---
263
264    private String info(Topic topic) {
265        return "topic " + topic.getId() + " (value=\"" + topic.getSimpleValue() + "\", typeUri=\"" +
266            topic.getTypeUri() + "\", uri=\"" + topic.getUri() + "\")";
267    }
268}