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