001package net.abriraqui.dm4.importexport;
002
003import com.sun.jersey.core.util.Base64;
004import de.deepamehta.core.Association;
005import de.deepamehta.core.Topic;
006import de.deepamehta.core.TopicType;
007import de.deepamehta.core.model.AssociationModel;
008import de.deepamehta.core.model.RoleModel;
009import de.deepamehta.core.model.TopicModel;
010import de.deepamehta.core.osgi.PluginActivator;
011import de.deepamehta.core.service.Inject;
012import de.deepamehta.core.service.Transactional;
013import de.deepamehta.plugins.files.FilesPlugin;
014import de.deepamehta.plugins.files.FilesService;
015import de.deepamehta.plugins.files.UploadedFile;
016import de.deepamehta.plugins.topicmaps.TopicmapsService;
017import de.deepamehta.plugins.topicmaps.model.AssociationViewmodel;
018import de.deepamehta.plugins.topicmaps.model.TopicViewmodel;
019import de.deepamehta.plugins.topicmaps.model.TopicmapViewmodel;
020import de.deepamehta.plugins.topicmaps.model.ViewProperties;
021import org.codehaus.jettison.json.JSONArray;
022import org.codehaus.jettison.json.JSONException;
023import org.codehaus.jettison.json.JSONObject;
024
025import javax.ws.rs.*;
026import javax.xml.stream.XMLStreamException;
027import java.io.ByteArrayInputStream;
028import java.io.ByteArrayOutputStream;
029import java.io.IOException;
030import java.io.InputStream;
031import java.io.File;
032import java.util.HashMap;
033import java.util.Map;
034import java.util.logging.Logger;
035
036@Path("/import-export")
037@Produces("application/json")
038public class ImportExportPlugin extends PluginActivator {
039
040    @Inject
041    private TopicmapsService topicmapsService;
042    @Inject
043    private FilesService filesService;
044
045    private Logger log = Logger.getLogger(getClass().getName());
046
047    // Service implementation //
048
049    @POST
050    @Transactional
051    @Path("/export/json")
052    public Topic exportTopicmapToJSON(@CookieParam("dm4_topicmap_id") long topicmapId) {
053        try {
054            log.info("Exporting Topicmap JSON ######### " + topicmapId);
055            TopicmapViewmodel topicmap = topicmapsService.getTopicmap(topicmapId, true);
056            String json = topicmap.toJSON().toString();
057            InputStream in = new ByteArrayInputStream(json.getBytes("UTF-8"));
058            String jsonFileName = "topicmap-" + topicmapId + ".txt";
059            return filesService.createFile(in, prefix() + "/" + jsonFileName);
060            // return filesService.createFile(in, jsonFileName);
061        } catch (Exception e) {
062            throw new RuntimeException("Export failed", e);
063        }
064    }
065
066    @POST
067    @Path("/export/svg")
068    @Transactional
069    public Topic exportTopicmapToSVG(@CookieParam("dm4_topicmap_id") long topicmapId) throws XMLStreamException {
070        final int BOX_HEIGHT = 20;
071        final int MARGIN_LEFT = 5;
072        final int MARGIN_TOP = 14;
073        final int ICON_WIDTH = 16;
074        final int ICON_HEIGHT = 16;
075        try {
076            log.info("Exporting Topicmap SVG ######### " + topicmapId);
077            // 0) Fetch topicmap data
078            TopicmapViewmodel topicmap = topicmapsService.getTopicmap(topicmapId, true);
079            Iterable<TopicViewmodel> topics = topicmap.getTopics();
080            Iterable<AssociationViewmodel> associations = topicmap.getAssociations();
081            // 1) Setup default file name of SVG to write to
082            String svgFileName = "Exported_Topicmap_" + topicmapId + ".svg";
083            // 2) Get DM4 filerepo configuration setting and write to document to root folder
084            String documentPath = filesService.getFile("/") + "/" + svgFileName;
085            // 3) Create SVGWriter
086            SVGRenderer svg = new SVGRenderer(documentPath);
087            svg.startGroupElement(topicmapId);
088            // 4) Create all associations
089            for (AssociationViewmodel association : associations) {
090                String valueAssoc = association.getSimpleValue().toString();
091                long topic1Id = association.getRoleModel1().getPlayerId();
092                long topic2Id = association.getRoleModel2().getPlayerId();
093                TopicViewmodel topic1 = topicmap.getTopic(topic1Id);
094                int x1 = topic1.getX();
095                int y1 = topic1.getY();
096                TopicViewmodel topic2 = topicmap.getTopic(topic2Id);
097                int x2 = topic2.getX();
098                int y2 = topic2.getY();
099                // 
100                int dx = x2 - x1;
101                int dy = y2 - y1;
102                int label_x = dx / 2;
103                int label_y = dy / 2;
104                double assocLine = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
105                double alpha = Math.asin(dy / assocLine) * 180 / Math.PI;
106                if (dx < 0) {
107                    alpha = -alpha;
108                }
109                svg.startGroupElement(association.getId());
110                svg.line(x1, x2, y1, y2);
111                svg.text(label_x, label_y, x1 + 10, y1 + 10, valueAssoc, "grey", alpha);
112                svg.endElement();
113            }
114            // 5) Create all topics
115            for (TopicViewmodel topic : topics) {
116                String value = topic.getSimpleValue().toString();
117                int x = topic.getX();
118                int y = topic.getY();
119                boolean visibility = topic.getVisibility();
120                int boxWidth = value.length() * 9;
121                if (!visibility) {
122                    continue;
123                }
124                svg.startGroupElement(topic.getId());
125                svg.rectangle(x - boxWidth / 2, y - BOX_HEIGHT / 2, boxWidth, BOX_HEIGHT, color(topic.getTypeUri()));
126                svg.text(x - boxWidth / 2 + MARGIN_LEFT, y - BOX_HEIGHT / 2 + MARGIN_TOP, value, "black");
127                svg.image(x + boxWidth / 2, y, ICON_WIDTH, ICON_HEIGHT, typeIconDataUri(topic.getTypeUri()));
128                svg.endElement();
129            }
130            // 6) Close SVGWriter
131            svg.endElement();
132            svg.closeDocument();
133            // 7) Create and return new file topic for the exported document
134            return filesService.getFileTopic(prefix() + "/" + svgFileName);
135        } catch (Exception e) {
136            throw new RuntimeException("Export Topicmap to SVG failed", e);
137        }
138    }
139
140    @POST
141    @Path("/import")
142    @Transactional
143    @Consumes("multipart/form-data")
144    public Topic importTopicmap(UploadedFile file) {
145        try {
146            String json = file.getString();
147
148            JSONObject topicmap = new JSONObject(json);
149            JSONObject info = topicmap.getJSONObject("info");
150
151            JSONArray assocsArray = topicmap.getJSONArray("assocs");
152            JSONArray topicsArray = topicmap.getJSONArray("topics");
153
154            String origTopicmapName = info.getString("value");
155            Topic importedTopicmap =
156                    topicmapsService.createTopicmap("Imported Topicmap: " + origTopicmapName
157                            , "dm4.webclient.default_topicmap_renderer");
158            long topicmapId = importedTopicmap.getId();
159            log.info("###### importedTopicmapId " + topicmapId);
160            // 
161            Map<Long, Long> mapTopicIds = new HashMap();
162            importTopics(topicsArray, mapTopicIds, topicmapId);
163            importAssociations(assocsArray, mapTopicIds, topicmapId);
164            return importedTopicmap;
165        } catch (Exception e) {
166            throw new RuntimeException("Importing Topicmap FAILED", e);
167        }
168    }
169
170    // Import topics
171    private void importTopics(JSONArray topicsArray, Map<Long, Long> mapTopicIds, long topicmapId) {
172        for (int i = 0, size = topicsArray.length(); i < size; i++) {
173            try {
174                JSONObject topic = topicsArray.getJSONObject(i);
175                createTopic(topic, mapTopicIds, topicmapId);
176            } catch (Exception e) {
177                log.warning("Topic NOT imported!!" + e);
178            }
179        }
180    }
181
182    // Import associations
183    private void importAssociations(JSONArray assocsArray, Map<Long, Long> mapTopicIds, long topicmapId) {
184        for (int i = 0, size = assocsArray.length(); i < size; i++) {
185            try {
186                JSONObject association = assocsArray.getJSONObject(i);
187                createAssociation(association, mapTopicIds, topicmapId);
188            } catch (Exception e) {
189                log.warning("Association NOT imported");
190            }
191        }
192    }
193
194    private String color(String typeUri) {
195        if (typeUri.equals("dm4.contacts.institution")) {
196            return "lightblue";
197        } else if (typeUri.equals("dm4.contacts.person")) {
198            return "lightblue";
199        } else if (typeUri.equals("dm4.notes.note")) {
200            return "lightblue";
201        } else {
202            return "lightblue";
203        }
204    }
205
206    /** ### Make this work for custom icons too, this works currently just with icons included in the standard
207     * distribution. */
208    private String typeIconDataUri(String typeUri) throws IOException {
209        TopicType topicType = dms.getTopicType(typeUri);
210        String iconPath = (String) topicType.getViewConfig("dm4.webclient.view_config", "dm4.webclient.icon");
211        InputStream iconIS = null;
212        // TODO: Load icons bundled in other plugins
213        // String pluginPath = iconPath.substring(1, sep);
214        // Plugin plugin = dms.getPlugin(pluginPath);
215        try {
216            int sep = iconPath.indexOf("/", 2); // Note: iconPath may be null and throw a NPE
217            String imagePath = "web" + iconPath.substring(sep);
218            iconIS = getStaticResource(imagePath);
219            log.fine("##### IconIS " + iconIS);
220        } catch (Exception e) {
221            // Icon resource not found in this plugin
222            log.info("### FALLBACK to standard grey icon as typeIcon for \""
223                    + typeUri + "\" icon could not be determined " + "during SVG Export");
224            iconIS = dms.getPlugin("de.deepamehta.webclient").getStaticResource("web/images/ball-gray.png");
225        }
226        // create base64 representation of the current type icon
227        ByteArrayOutputStream baos = new ByteArrayOutputStream();
228        byte[] buffer = new byte[1024];
229        int count = 0;
230        while ((count = iconIS.read(buffer)) != -1) {
231            baos.write(buffer, 0, count);
232        }
233        byte[] fileContent = baos.toByteArray();
234        // all chars in encoded are guaranteed to be 7-bit ASCII
235        byte[] encoded = Base64.encode(fileContent);
236        String imgBase64Str = new String(encoded);
237        log.fine("##### IMG BASE64 " + imgBase64Str);
238        //
239        return "data:image/png;base64," + imgBase64Str;
240    }
241
242    private void createTopic(JSONObject topic, Map<Long, Long> mapTopicIds, long topicmapId) throws JSONException {
243        TopicModel model = new TopicModel(topic);
244        ViewProperties viewProps = new ViewProperties(topic.getJSONObject("view_props"));
245        long origTopicId = model.getId();
246        Topic newTopic = dms.createTopic(model);
247        long topicId = newTopic.getId();
248        mapTopicIds.put(origTopicId, topicId);
249        topicmapsService.addTopicToTopicmap(topicmapId, topicId, viewProps);
250    }
251
252    private void createAssociation(JSONObject association, Map<Long, Long> mapTopicIds, long topicmapId) {
253        AssociationModel assocModel = new AssociationModel(association);
254        RoleModel role1 = assocModel.getRoleModel1();
255        role1.setPlayerId(mapTopicIds.get(role1.getPlayerId()));
256        RoleModel role2 = assocModel.getRoleModel2();
257        role2.setPlayerId(mapTopicIds.get(role2.getPlayerId()));
258        Association newAssociation = dms.createAssociation(assocModel);
259        long assocId = newAssociation.getId();
260        topicmapsService.addAssociationToTopicmap(topicmapId, assocId);
261    }
262
263    private String prefix() {
264        File repo = filesService.getFile("/");
265        return ((FilesPlugin) filesService).repoPath(repo);
266    }
267
268}