769e807075b16b5e963fa2e1b2bee13fda338ba4
[gpl/argeo-suite.git] / org.argeo.suite.workbench.rap / src / org / argeo / suite / workbench / commands / ImportEntities.java
1 package org.argeo.suite.workbench.commands;
2
3 import static org.argeo.connect.util.JxlUtils.getStringValue;
4 import static org.argeo.eclipse.ui.EclipseUiUtils.notEmpty;
5
6 import java.io.File;
7 import java.io.FileInputStream;
8 import java.io.FileOutputStream;
9 import java.io.IOException;
10 import java.io.InputStream;
11 import java.io.OutputStream;
12 import java.util.Collections;
13 import java.util.HashMap;
14 import java.util.Map;
15 import java.util.UUID;
16
17 import javax.jcr.Node;
18 import javax.jcr.Property;
19 import javax.jcr.Repository;
20 import javax.jcr.RepositoryException;
21 import javax.jcr.Session;
22
23 import org.apache.commons.io.IOUtils;
24 import org.argeo.connect.ConnectConstants;
25 import org.argeo.connect.ConnectNames;
26 import org.argeo.connect.resources.ResourcesNames;
27 import org.argeo.connect.resources.ResourcesService;
28 import org.argeo.connect.util.ConnectJcrUtils;
29 import org.argeo.connect.util.JxlUtils;
30 import org.argeo.eclipse.ui.EclipseUiUtils;
31 import org.argeo.jcr.JcrUtils;
32 import org.argeo.people.ContactValueCatalogs;
33 import org.argeo.people.PeopleException;
34 import org.argeo.people.PeopleNames;
35 import org.argeo.people.PeopleService;
36 import org.argeo.people.PeopleTypes;
37 import org.argeo.people.util.PeopleJcrUtils;
38 import org.argeo.people.util.PersonJcrUtils;
39 import org.argeo.suite.SuiteException;
40 import org.argeo.suite.workbench.AsUiPlugin;
41 import org.eclipse.core.commands.AbstractHandler;
42 import org.eclipse.core.commands.ExecutionEvent;
43 import org.eclipse.core.commands.ExecutionException;
44 import org.eclipse.jface.wizard.Wizard;
45 import org.eclipse.jface.wizard.WizardDialog;
46 import org.eclipse.jface.wizard.WizardPage;
47 import org.eclipse.rap.fileupload.FileDetails;
48 import org.eclipse.rap.fileupload.FileUploadEvent;
49 import org.eclipse.rap.fileupload.FileUploadHandler;
50 import org.eclipse.rap.fileupload.FileUploadListener;
51 import org.eclipse.rap.fileupload.FileUploadReceiver;
52 import org.eclipse.rap.rwt.service.ServerPushSession;
53 import org.eclipse.rap.rwt.widgets.FileUpload;
54 import org.eclipse.swt.SWT;
55 import org.eclipse.swt.events.ModifyEvent;
56 import org.eclipse.swt.events.ModifyListener;
57 import org.eclipse.swt.events.SelectionAdapter;
58 import org.eclipse.swt.events.SelectionEvent;
59 import org.eclipse.swt.layout.GridData;
60 import org.eclipse.swt.layout.GridLayout;
61 import org.eclipse.swt.widgets.Combo;
62 import org.eclipse.swt.widgets.Composite;
63 import org.eclipse.swt.widgets.Control;
64 import org.eclipse.swt.widgets.Label;
65 import org.eclipse.swt.widgets.Shell;
66 import org.eclipse.ui.handlers.HandlerUtil;
67
68 import jxl.Sheet;
69 import jxl.Workbook;
70
71 /** Open a one page wizard to import an EXCEL 2003 legacy organisation file */
72 public class ImportEntities extends AbstractHandler implements PeopleNames {
73
74 public final static String ID = AsUiPlugin.PLUGIN_ID + ".importEntities";
75
76 // public final static String PARAM_NODE_TYPE = "param.nodeType";
77
78 private static final Map<String, String> KNOWN_TEMPLATES;
79 static {
80 Map<String, String> tmpMap = new HashMap<String, String>();
81 tmpMap.put("Organisations", PeopleTypes.PEOPLE_ORG);
82 KNOWN_TEMPLATES = Collections.unmodifiableMap(tmpMap);
83 }
84
85 // TODO make this configurable
86 private final static String IMPORT_ENCODING = "ISO-8859-1";// "UTF-8";
87
88 /* DEPENDENCY INJECTION */
89 private Repository repository;
90 private ResourcesService resourcesService;
91 private PeopleService peopleService;
92
93 public Object execute(final ExecutionEvent event) throws ExecutionException {
94 // String jcrId = event.getParameter(PARAM_NODE_TYPE);
95
96 Wizard wizard = new ImportMappingFileWizard(HandlerUtil.getActiveShell(event),
97 "Upload legacy contact via Excel file import");
98 WizardDialog dialog = new WizardDialog(HandlerUtil.getActiveShell(event), wizard);
99 dialog.open();
100 return null;
101 }
102
103 /** One page wizard to import a EXCEL 2003 Mapping files */
104 private class ImportMappingFileWizard extends Wizard {
105
106 // Various UI Objects
107 private UserInputPage userInputPage;
108 private Combo resourceTypeCombo;
109
110 // File upload
111 private FileUpload fileUpload;
112 private Label fileNameLabel;
113 private ServerPushSession pushSession;
114 private File file;
115
116 public ImportMappingFileWizard(Shell parentShell, String title) {
117 setWindowTitle(title);
118 }
119
120 @Override
121 public void addPages() {
122 try {
123 userInputPage = new UserInputPage("User input page");
124 addPage(userInputPage);
125 } catch (Exception e) {
126 throw new SuiteException("Cannot add page to wizard", e);
127 }
128 }
129
130 /** Performs the real import. */
131 @Override
132 public boolean performFinish() {
133 // Session session = null;
134 // String templateName =
135 // resourceTypeCombo.getItem(resourceTypeCombo.getSelectionIndex());
136 // String type = KNOWN_TEMPLATES.get(templateName);
137 importDefaultOrgFile(file);
138 return true;
139 }
140
141 @Override
142 public boolean performCancel() {
143 return true;
144 }
145
146 @Override
147 public boolean canFinish() {
148 if (resourceTypeCombo.getSelectionIndex() < 0) {
149 userInputPage.setErrorMessage("Please choose an entity type");
150 return false;
151 } else if (file == null) {
152 userInputPage.setErrorMessage("Please upload a file");
153 return false;
154 } else {
155 userInputPage.setErrorMessage(null);
156 return true;
157 }
158 }
159
160 private class UserInputPage extends WizardPage {
161 private static final long serialVersionUID = 1L;
162
163 public UserInputPage(String pageName) {
164 super(pageName);
165 setTitle("Upload an Excel 2003 file (.xls)");
166 }
167
168 public void createControl(Composite parent) {
169 parent.setLayout(new GridLayout(1, false));
170 Composite composite = new Composite(parent, SWT.NONE);
171 composite.setLayout(new GridLayout(2, false));
172 composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
173
174 // Import type
175 resourceTypeCombo = createLC(composite, "Type");
176 resourceTypeCombo.addModifyListener(new ModifyListener() {
177 private static final long serialVersionUID = 1L;
178
179 @Override
180 public void modifyText(ModifyEvent event) {
181 getWizard().getContainer().updateButtons();
182 }
183 });
184 resourceTypeCombo.setItems(KNOWN_TEMPLATES.keySet().toArray(new String[0]));
185 resourceTypeCombo.select(0);
186
187 // File upload
188 Label lbl = new Label(composite, SWT.NONE);
189 lbl.setText("Chosen file");
190 lbl.setFont(EclipseUiUtils.getBoldFont(composite));
191 Composite uploadCmp = new Composite(composite, SWT.NONE);
192 uploadCmp.setLayoutData(EclipseUiUtils.fillWidth());
193 createFileUploadArea(uploadCmp);
194 setControl(composite);
195 }
196 }
197
198 private Control createFileUploadArea(Composite parent) {
199 GridLayout gl = EclipseUiUtils.noSpaceGridLayout(new GridLayout(2, false));
200 gl.horizontalSpacing = 5;
201 parent.setLayout(gl);
202
203 fileNameLabel = new Label(parent, SWT.NONE | SWT.BEGINNING);
204 fileNameLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
205
206 fileUpload = new FileUpload(parent, SWT.NONE);
207 fileUpload.setText("Browse...");
208 fileUpload.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
209
210 final String url = startUploadReceiver();
211 pushSession = new ServerPushSession();
212
213 fileUpload.addSelectionListener(new SelectionAdapter() {
214 private static final long serialVersionUID = 1L;
215
216 @Override
217 public void widgetSelected(SelectionEvent e) {
218 String fileName = fileUpload.getFileName();
219 fileNameLabel.setText(fileName == null ? "" : fileName);
220 pushSession.start();
221 fileUpload.submit(url);
222 }
223 });
224 return parent;
225 }
226
227 private String startUploadReceiver() {
228 MyFileUploadReceiver receiver = new MyFileUploadReceiver();
229 FileUploadHandler uploadHandler = new FileUploadHandler(receiver);
230 uploadHandler.addUploadListener(new FileUploadListener() {
231
232 public void uploadProgress(FileUploadEvent event) {
233 // handle upload progress
234 }
235
236 public void uploadFailed(FileUploadEvent event) {
237 ImportMappingFileWizard.this.userInputPage
238 .setErrorMessage("upload failed: " + event.getException());
239 }
240
241 public void uploadFinished(FileUploadEvent event) {
242 fileNameLabel.getDisplay().asyncExec(new Runnable() {
243 public void run() {
244 ImportMappingFileWizard.this.getContainer().updateButtons();
245 pushSession.stop();
246 }
247 });
248 }
249 });
250 return uploadHandler.getUploadUrl();
251 }
252
253 private class MyFileUploadReceiver extends FileUploadReceiver {
254
255 private static final String TEMP_FILE_PREFIX = "fileupload_";
256
257 @Override
258 public void receive(InputStream dataStream, FileDetails details) throws IOException {
259 File result = File.createTempFile(TEMP_FILE_PREFIX, "");
260 FileOutputStream outputStream = new FileOutputStream(result);
261 try {
262 copy(dataStream, outputStream);
263 } finally {
264 dataStream.close();
265 outputStream.close();
266 }
267 if (file != null)
268 file.delete();
269 file = result;
270 }
271 }
272
273 private void copy(InputStream inputStream, OutputStream outputStream) throws IOException {
274 byte[] buffer = new byte[8192];
275 boolean finished = false;
276 while (!finished) {
277 int bytesRead = inputStream.read(buffer);
278 if (bytesRead != -1) {
279 outputStream.write(buffer, 0, bytesRead);
280 } else {
281 finished = true;
282 }
283 }
284 }
285
286 /** Creates label and Combo. */
287 protected Combo createLC(Composite parent, String label) {
288 Label lbl = new Label(parent, SWT.RIGHT);
289 lbl.setText(label);
290 lbl.setFont(EclipseUiUtils.getBoldFont(parent));
291 lbl.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));
292 Combo combo = new Combo(parent, SWT.READ_ONLY);
293 combo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
294 return combo;
295 }
296 }
297
298 // Legacy Organisations
299 private Node importDefaultOrgFile(File file) {
300 InputStream in = null;
301 try {
302 in = new FileInputStream(file);
303 return importDefaultOrgFile(in);
304 } catch (IOException e) {
305 throw new SuiteException("Cannot import mapping file", e);
306 } finally {
307 IOUtils.closeQuietly(in);
308 }
309 }
310
311 private Node createDraftNode(Node parent, String mainMixin) throws RepositoryException {
312 String uuid = UUID.randomUUID().toString();
313 Node tmpNode = parent.addNode(uuid);
314 tmpNode.addMixin(mainMixin);
315 tmpNode.setProperty(ConnectNames.CONNECT_UID, uuid);
316 return tmpNode;
317 }
318
319 private void importOrgEmployees(Node tmpParent, Node targetParent, Node newOrgNode, String coworkersStr)
320 throws RepositoryException {
321 String[] coworkers = coworkersStr.split("\\n");
322 loop: for (String line : coworkers) {
323 if (EclipseUiUtils.isEmpty(line))
324 continue loop;
325 line = line.trim();
326 int index = line.indexOf(' ');
327 String firstName = null;
328 String lastName = null;
329 String position = null;
330 if (index == -1)
331 firstName = line;
332 else {
333 firstName = line.substring(0, index);
334 line = line.substring(index);
335
336 index = line.indexOf('(');
337 if (index == -1)
338 lastName = line;
339 else {
340 lastName = line.substring(0, index).trim();
341 position = line.substring(index + 1, line.length() - 1);
342 }
343 }
344 Node tmpPerson = createDraftNode(tmpParent, PeopleTypes.PEOPLE_PERSON);
345 tmpPerson.setProperty(PEOPLE_FIRST_NAME, firstName);
346 if (EclipseUiUtils.notEmpty(lastName))
347 tmpPerson.setProperty(PEOPLE_LAST_NAME, lastName);
348 Node newPersonNode = peopleService.publishEntity(targetParent, PeopleTypes.PEOPLE_PERSON, tmpPerson);
349 // if (EclipseUiUtils.notEmpty(position))
350 PersonJcrUtils.addJob(resourcesService, peopleService, newPersonNode, newOrgNode, position, true);
351 // Save the newly created entity without creating a base version
352 newPersonNode = peopleService.saveEntity(newPersonNode, false);
353
354 }
355 }
356
357 private void importUrls(Node contactable, String urlStr) throws RepositoryException {
358 String[] urls = urlStr.split("\\n");
359 boolean hasPrimary = false;
360 boolean hasPrimaryFacebook = false;
361
362 loop: for (String line : urls) {
363 if (EclipseUiUtils.isEmpty(line))
364 continue loop;
365 line = line.trim();
366
367 if (line.startsWith("https://www.facebook.com")) {
368 PeopleJcrUtils.createSocialMedia(resourcesService, peopleService, contactable, line,
369 !hasPrimaryFacebook, ContactValueCatalogs.CONTACT_CAT_FACEBOOK, null);
370 hasPrimaryFacebook = true;
371 } else {
372 PeopleJcrUtils.createWebsite(resourcesService, peopleService, contactable, line, !hasPrimary, null);
373 hasPrimary = true;
374 }
375 }
376 }
377
378 private void importMails(Node contactable, String mailStr) throws RepositoryException {
379 String[] urls = mailStr.split("\\n");
380 boolean hasPrimary = false;
381 loop: for (String line : urls) {
382 if (EclipseUiUtils.isEmpty(line))
383 continue loop;
384 line = line.trim();
385 PeopleJcrUtils.createEmail(resourcesService, peopleService, contactable, line, !hasPrimary, null, null);
386 hasPrimary = true;
387 }
388 }
389
390 // TODO make this configurable
391 int displayNameIndex = 0;
392 int legalNameIndex = 1;
393 int legalFormIndex = 2;
394 int urlsIndex = 3;
395 int streetIndex = 4;
396 int postalCodeIndex = 5;
397 int lIndex = 6;
398 int stIndex = 7;
399 int cIndex = 8;
400 int mobileIndex = 9;
401 int telephoneNumberIndex = 10;
402 int mailIndex = 11;
403 int contactsIndex = 12;
404 int descriptionIndex = 13;
405 int tagsIndex = 14;
406
407 private Node importDefaultOrgFile(InputStream in) throws IOException {
408 Session session = null;
409 int i = 0;
410 try {
411 Workbook wb = JxlUtils.toWorkbook(in, IMPORT_ENCODING);
412 session = repository.login();
413 String basePath = "/" + peopleService.getBaseRelPath(PeopleTypes.PEOPLE_ORG);
414 Node targetParent = session.getNode(basePath);
415 Sheet sheet = wb.getSheet(0);
416
417 Node tmpParent = peopleService.getDraftParent(session);
418
419 int rowNb = sheet.getRows();
420 for (i = 1; i < rowNb - 1; i++) {
421
422 Node tmpOrg = createDraftNode(tmpParent, PeopleTypes.PEOPLE_ORG);
423
424 String dName = JxlUtils.getStringValue(sheet, displayNameIndex, i);
425 if (notEmpty(dName))
426 tmpOrg.setProperty(PeopleNames.PEOPLE_DISPLAY_NAME, dName);
427 String lName = getStringValue(sheet, legalNameIndex, i);
428 if (notEmpty(lName))
429 tmpOrg.setProperty(PeopleNames.PEOPLE_LEGAL_NAME, lName);
430 String lForm = getStringValue(sheet, legalFormIndex, i);
431 if (notEmpty(lForm))
432 tmpOrg.setProperty(PeopleNames.PEOPLE_LEGAL_FORM, lForm);
433 String urlStr = getStringValue(sheet, urlsIndex, i);
434 if (notEmpty(urlStr))
435 importUrls(tmpOrg, urlStr);
436 String mailStr = getStringValue(sheet, mailIndex, i);
437 if (notEmpty(mailStr))
438 importMails(tmpOrg, mailStr);
439 String streetStr = getStringValue(sheet, streetIndex, i);
440 String pcStr = getStringValue(sheet, postalCodeIndex, i);
441 String lStr = getStringValue(sheet, lIndex, i);
442 String stStr = getStringValue(sheet, stIndex, i);
443 String cStr = getStringValue(sheet, cIndex, i);
444 if (notEmpty(streetStr) || notEmpty(pcStr) || notEmpty(lStr) || notEmpty(stStr) || notEmpty(cStr))
445 PeopleJcrUtils.createAddress(resourcesService, peopleService, tmpOrg, streetStr, null, pcStr, lStr,
446 stStr, cStr, true, ContactValueCatalogs.CONTACT_CAT_MAIN, null);
447 String mobileStr = getStringValue(sheet, mobileIndex, i);
448 if (notEmpty(mobileStr))
449 PeopleJcrUtils.createPhone(resourcesService, peopleService, tmpOrg, mobileStr, true, null, null);
450 String phoneStr = getStringValue(sheet, telephoneNumberIndex, i);
451 if (notEmpty(phoneStr))
452 PeopleJcrUtils.createPhone(resourcesService, peopleService, tmpOrg, phoneStr, true,
453 ContactValueCatalogs.CONTACT_CAT_DIRECT, null);
454 String descStr = getStringValue(sheet, descriptionIndex, i);
455 if (notEmpty(descStr))
456 tmpOrg.setProperty(Property.JCR_DESCRIPTION, descStr);
457 String tagsStr = getStringValue(sheet, tagsIndex, i);
458 if (notEmpty(tagsStr))
459 tmpOrg.setProperty(ResourcesNames.CONNECT_TAGS, ConnectJcrUtils.parseAndClean(tagsStr, ",", true));
460
461 Node newOrgNode = peopleService.publishEntity(targetParent, PeopleTypes.PEOPLE_ORG, tmpOrg);
462 // Save the newly created entity without creating a base version
463 newOrgNode = peopleService.saveEntity(newOrgNode, false);
464
465 String contactsStr = getStringValue(sheet, contactsIndex, i);
466 if (notEmpty(contactsStr))
467 importOrgEmployees(tmpParent, targetParent, newOrgNode, contactsStr);
468 }
469
470 // Refresh tags and mailing list
471 Node tagParent = resourcesService.getTagLikeResourceParent(session, ConnectConstants.RESOURCE_TAG);
472 resourcesService.refreshKnownTags(tagParent);
473
474 // Create Mailing lists
475 Node mlParent = resourcesService.getTagLikeResourceParent(session, PeopleTypes.PEOPLE_MAILING_LIST);
476 resourcesService.refreshKnownTags(mlParent);
477
478 } catch (PeopleException | RepositoryException e) {
479 throw new SuiteException("Cannot import mapping file, error at line: " + (i + 1), e);
480 } finally {
481 JcrUtils.logoutQuietly(session);
482 }
483
484 return null;
485 }
486
487 /* DEPENDENCY INJECTION */
488 public void setRepository(Repository repository) {
489 this.repository = repository;
490 }
491
492 public void setResourcesService(ResourcesService resourcesService) {
493 this.resourcesService = resourcesService;
494 }
495
496 public void setPeopleService(PeopleService peopleService) {
497 this.peopleService = peopleService;
498 }
499 }