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}