001    package de.deepamehta.plugins.csv;
002    
003    import java.io.File;
004    import java.io.FileNotFoundException;
005    import java.io.FileReader;
006    import java.io.IOException;
007    import java.util.ArrayList;
008    import java.util.Arrays;
009    import java.util.HashMap;
010    import java.util.List;
011    import java.util.Map;
012    import java.util.logging.Logger;
013    
014    import javax.ws.rs.HeaderParam;
015    import javax.ws.rs.POST;
016    import javax.ws.rs.Path;
017    import javax.ws.rs.PathParam;
018    import javax.ws.rs.WebApplicationException;
019    
020    import au.com.bytecode.opencsv.CSVReader;
021    import de.deepamehta.core.AssociationDefinition;
022    import de.deepamehta.core.RelatedTopic;
023    import de.deepamehta.core.TopicType;
024    import de.deepamehta.core.model.CompositeValueModel;
025    import de.deepamehta.core.model.TopicModel;
026    import de.deepamehta.core.osgi.PluginActivator;
027    import de.deepamehta.core.service.ClientState;
028    import de.deepamehta.core.service.PluginService;
029    import de.deepamehta.core.service.annotation.ConsumesService;
030    import de.deepamehta.core.storage.spi.DeepaMehtaTransaction;
031    import de.deepamehta.plugins.files.ResourceInfo;
032    import de.deepamehta.plugins.files.service.FilesService;
033    import javax.ws.rs.Produces;
034    import javax.ws.rs.core.MediaType;
035    
036    @Path("csv")
037    @Produces(MediaType.APPLICATION_JSON)
038    public class CsvPlugin extends PluginActivator {
039    
040        private static Logger log = Logger.getLogger(CsvPlugin.class.getName());
041    
042        public static final String FOLDER = "csv";
043    
044        public static final char SEPARATOR = '|';
045    
046        private boolean isInitialized;
047    
048        private FilesService fileService;
049    
050        @POST
051        @Path("import/{uri}/{file}")
052        public ImportStatus importCsv(//
053                @PathParam("uri") String typeUri,//
054                @PathParam("file") long fileId,//
055                @HeaderParam("Cookie") ClientState cookie) {
056    
057            DeepaMehtaTransaction tx = dms.beginTx();
058            try {
059                List<String[]> lines = readCsvFile(fileId);
060                if (lines.size() < 2) {
061                    return new ImportStatus(false, "please upload a valid CSV, see README", null);
062                }
063    
064                List<String> childTypeUris = Arrays.asList(lines.get(0));
065                String uriPrefix = childTypeUris.get(0);
066    
067                Map<String, Long> topicsByUri = getTopicIdsByUri(typeUri);
068                Map<String, Map<String, Long>> aggrIdsByTypeUriAndValue = getPossibleAggrChilds(typeUri, childTypeUris);
069    
070                // status informations
071                int created = 0, deleted = 0, updated = 0;
072    
073                // persist each row
074                for (int r = 1; r < lines.size(); r++) {
075                    String[] row = lines.get(r);
076                    String topicUri = uriPrefix + "." + row[0];
077    
078                    // create a fresh model
079                    TopicModel model = new TopicModel(typeUri);
080                    model.setUri(topicUri);
081    
082                    // map all columns to composite value
083                    CompositeValueModel value = new CompositeValueModel();
084                    for (int c = 1; c < row.length; c++) {
085                        String childTypeUri = childTypeUris.get(c);
086                        String childValue = row[c];
087    
088                        // reference or create a child
089                        Map<String, Long> aggrIdsByValue = aggrIdsByTypeUriAndValue.get(childTypeUri);
090                        if (aggrIdsByValue != null && aggrIdsByValue.get(childValue) != null) {
091                            value.putRef(childTypeUri, aggrIdsByValue.get(childValue));
092                        } else {
093                            value.put(childTypeUri, childValue);
094                        }
095                    }
096                    model.setCompositeValue(value);
097    
098                    // create or update a topic
099                    Long topicId = topicsByUri.get(topicUri);
100                    if (topicId == null) { // create
101                        dms.createTopic(model, cookie);
102                        created++;
103                    } else { // update topic and remove from map
104                        model.setId(topicId);
105                        topicsByUri.remove(topicUri);
106                        dms.updateTopic(model, cookie);
107                        updated++;
108                    }
109                }
110    
111                // delete the remaining instances
112                for (String topicUri : topicsByUri.keySet()) {
113                    Long topicId = topicsByUri.get(topicUri);
114                    dms.deleteTopic(topicId);
115                    deleted++;
116                }
117    
118                List<String> status = new ArrayList<String>();
119                status.add("created: " + created);
120                status.add("updated: " + updated);
121                status.add("deleted: " + deleted);
122    
123                tx.success();
124                return new ImportStatus(true, "SUCCESS", status);
125            } catch (IOException e) {
126                throw new RuntimeException(e) ;
127            } finally {
128                tx.finish();
129            }
130        }
131    
132        /**
133         * get all possible aggregation instances and hash them by typeUri and value
134         * 
135         * @param typeUri
136         * @param childTypeUris
137         * @return
138         */
139        private Map<String, Map<String, Long>> getPossibleAggrChilds(String typeUri, List<String> childTypeUris) {
140            TopicType topicType = dms.getTopicType(typeUri);
141            Map<String, Map<String, Long>> aggrIdsByTypeUriAndValue = new HashMap<String, Map<String, Long>>();
142            for (AssociationDefinition associationDefinition : topicType.getAssocDefs()) {
143                if (associationDefinition.getTypeUri().equals("dm4.core.aggregation_def")) {
144                    String childTypeUri = associationDefinition.getChildTypeUri();
145                    if (childTypeUris.contains(childTypeUri)) {
146                        aggrIdsByTypeUriAndValue.put(childTypeUri, getTopicIdsByValue(childTypeUri));
147                    }
148                }
149            }
150            return aggrIdsByTypeUriAndValue;
151        }
152    
153        /**
154         * get all existing instance topics and hash them by value
155         * 
156         * @param childTypeUri
157         * @return instance topics hashed by value
158         */
159        private Map<String, Long> getTopicIdsByValue(String childTypeUri) {
160            Map<String, Long> idsByValue = new HashMap<String, Long>();
161            for (RelatedTopic instance : dms.getTopics(childTypeUri, false, 0).getItems()) {
162                idsByValue.put(instance.getSimpleValue().toString(), instance.getId());
163            }
164            return idsByValue;
165        }
166    
167        /**
168         * get all existing instance topics and hash them by URI
169         * 
170         * @param typeUri
171         * @return instance topics hashed by URI
172         */
173        private Map<String, Long> getTopicIdsByUri(String typeUri) {
174            Map<String, Long> idsByUri = new HashMap<String, Long>();
175            for (RelatedTopic topic : dms.getTopics(typeUri, false, 0).getItems()) {
176                String topicUri = topic.getUri();
177                if (topicUri != null && topicUri.isEmpty() == false) {
178                    idsByUri.put(topicUri, topic.getId());
179                }
180            }
181            return idsByUri;
182        }
183    
184        /**
185         * read and validate CSV file
186         * 
187         * @param fileId
188         * @return parsed CSV rows with trimmed column array
189         * 
190         * @throws FileNotFoundException
191         * @throws IOException
192         */
193        private List<String[]> readCsvFile(long fileId) throws IOException {
194            String fileName = fileService.getFile(fileId).getAbsolutePath();
195            log.info("read CSV " + fileName);
196            CSVReader csvReader = new CSVReader(new FileReader(fileName), SEPARATOR);
197            List<String[]> lines = csvReader.readAll();
198            csvReader.close();
199    
200            // trim all columns
201            for (String[] row : lines) {
202                for (int col = 0; col < row.length; col++) {
203                    row[col] = row[col].trim();
204                }
205            }
206            return lines;
207        }
208    
209        /**
210         * Initialize.
211         */
212        @Override
213        public void init() {
214            isInitialized = true;
215            configureIfReady();
216        }
217    
218        @Override
219        @ConsumesService("de.deepamehta.plugins.files.service.FilesService")
220        public void serviceArrived(PluginService service) {
221            if (service instanceof FilesService) {
222                fileService = (FilesService) service;
223            }
224            configureIfReady();
225        }
226    
227        @Override
228        public void serviceGone(PluginService service) {
229            if (service == fileService) {
230                fileService = null;
231            }
232        }
233    
234        private void configureIfReady() {
235            if (isInitialized && fileService != null) {
236                createCsvDirectory();
237            }
238        }
239    
240        private void createCsvDirectory() {
241            // TODO move the initialization to migration "0"
242            try {
243                ResourceInfo resourceInfo = fileService.getResourceInfo(FOLDER);
244                String kind = resourceInfo.toJSON().getString("kind");
245                if (kind.equals("directory") == false) {
246                    String repoPath = System.getProperty("dm4.filerepo.path");
247                    throw new IllegalStateException("CSV storage directory " + //
248                            repoPath + File.separator + FOLDER + " can not be used");
249                }
250            } catch (WebApplicationException e) { // !exists
251                // catch fileService info request error => create directory
252                if (e.getResponse().getStatus() != 404) {
253                    throw e;
254                } else {
255                    log.info("create CSV directory");
256                    fileService.createFolder(FOLDER, "/");
257                }
258            } catch (Exception e) {
259                throw new RuntimeException(e);
260            }
261        }
262    }