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