1 package com.eyeq.pivot4j.pentaho.ui;
2
3 import java.io.IOException;
4 import java.io.InputStream;
5 import java.io.Serializable;
6 import java.util.LinkedList;
7 import java.util.List;
8 import java.util.ResourceBundle;
9
10 import javax.faces.FacesException;
11 import javax.faces.application.FacesMessage;
12 import javax.faces.bean.ManagedBean;
13 import javax.faces.bean.ManagedProperty;
14 import javax.faces.bean.ViewScoped;
15 import javax.faces.context.FacesContext;
16 import javax.faces.context.Flash;
17
18 import org.apache.commons.configuration.ConfigurationException;
19 import org.apache.commons.configuration.HierarchicalConfiguration;
20 import org.apache.commons.configuration.XMLConfiguration;
21 import org.apache.commons.io.IOUtils;
22 import org.olap4j.OlapDataSource;
23 import org.primefaces.context.RequestContext;
24 import org.primefaces.model.DefaultTreeNode;
25 import org.primefaces.model.TreeNode;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
28
29 import com.eyeq.pivot4j.PivotModel;
30 import com.eyeq.pivot4j.analytics.config.Settings;
31 import com.eyeq.pivot4j.analytics.datasource.ConnectionInfo;
32 import com.eyeq.pivot4j.analytics.datasource.CubeInfo;
33 import com.eyeq.pivot4j.analytics.datasource.DataSourceManager;
34 import com.eyeq.pivot4j.analytics.repository.DataSourceNotFoundException;
35 import com.eyeq.pivot4j.analytics.repository.ReportContent;
36 import com.eyeq.pivot4j.analytics.repository.ReportFile;
37 import com.eyeq.pivot4j.analytics.repository.ReportRepository;
38 import com.eyeq.pivot4j.analytics.repository.RepositoryFileFilter;
39 import com.eyeq.pivot4j.analytics.state.ViewState;
40 import com.eyeq.pivot4j.analytics.state.ViewStateHolder;
41 import com.eyeq.pivot4j.analytics.ui.navigator.RepositoryNode;
42 import com.eyeq.pivot4j.impl.PivotModelImpl;
43 import com.eyeq.pivot4j.mdx.MdxParser;
44 import com.eyeq.pivot4j.mdx.MdxStatement;
45 import com.eyeq.pivot4j.mdx.impl.MdxParserImpl;
46 import com.eyeq.pivot4j.ui.table.TableRenderer;
47
48 @ManagedBean(name = "migrationHandler")
49 @ViewScoped
50 public class MigrationHandler {
51
52 private Logger log = LoggerFactory.getLogger(getClass());
53
54 @ManagedProperty(value = "#{dataSourceManager}")
55 private DataSourceManager dataSourceManager;
56
57 @ManagedProperty(value = "#{settings}")
58 private Settings settings;
59
60 @ManagedProperty(value = "#{reportRepository}")
61 private ReportRepository repository;
62
63 @ManagedProperty(value = "#{viewStateHolder}")
64 private ViewStateHolder viewStateHolder;
65
66 private TreeNode rootNode;
67
68 private TreeNode selection;
69
70 private String viewId;
71
72 private List<Entry> convertedFiles = new LinkedList<Entry>();
73
74 private Entry selectedFile;
75
76 private boolean migrationDone = false;
77
78 private RepositoryFileFilter fileFilter = new MigrationTargetFilter();
79
80 public boolean isOkButtonEnabled() {
81 if (migrationDone) {
82 return selectedFile != null && selectedFile.getError() == null;
83 } else {
84 return selection != null;
85 }
86 }
87
88 public String proceed() {
89 FacesContext context = FacesContext.getCurrentInstance();
90 ResourceBundle bundle = context.getApplication().getResourceBundle(
91 context, "plugin_msg");
92
93 String navigation = null;
94
95 try {
96 if (migrationDone) {
97 navigation = open(selectedFile.getResult(), false);
98 } else {
99 convertedFiles.clear();
100
101 ReportFile target;
102
103 if (selection instanceof RepositoryNode) {
104 target = ((RepositoryNode) selection).getObject();
105 } else {
106 target = repository.getRoot();
107 }
108
109 if (target.isDirectory()) {
110 convertFiles(target);
111 } else {
112 navigation = open(target, true);
113 }
114
115 RequestContext.getCurrentInstance().execute(
116 "if (parent) parent.mantle_refreshRepository()");
117
118 this.migrationDone = true;
119 }
120 } catch (Exception e) {
121 String title = bundle.getString("title.migrate.error");
122 String message = bundle.getString("message.migrate.error") + e;
123
124 context.addMessage(null, new FacesMessage(
125 FacesMessage.SEVERITY_ERROR, title, message));
126
127 if (log.isErrorEnabled()) {
128 log.error(message, e);
129 }
130 }
131
132 return navigation;
133 }
134
135 public String getSummary() {
136 FacesContext context = FacesContext.getCurrentInstance();
137 ResourceBundle bundle = context.getApplication().getResourceBundle(
138 context, "plugin_msg");
139
140 int count = 0;
141
142 for (Entry entry : convertedFiles) {
143 if (entry.getError() == null) {
144 count++;
145 }
146 }
147
148 String message = String.format(
149 bundle.getString("message.migrate.success"), count);
150
151 return message;
152 }
153
154
155
156
157
158
159
160
161
162
163 public String open(ReportFile file, boolean convert) throws IOException,
164 ConfigurationException, DataSourceNotFoundException,
165 InvalidConnectionInfoException {
166 FacesContext context = FacesContext.getCurrentInstance();
167
168 ViewState state;
169
170 if (convert) {
171 state = convertFile(file);
172 state.setDirty(true);
173 } else {
174 state = new ViewState(viewId, file.getName());
175 state.setFile(file);
176
177 ReportContent content = repository.getReportContent(file);
178 content.read(state, dataSourceManager);
179 }
180
181 state.getModel().initialize();
182
183 viewStateHolder.registerState(state);
184
185 Flash flash = context.getExternalContext().getFlash();
186
187 flash.put("connectionInfo", state.getConnectionInfo());
188 flash.put("viewId", state.getId());
189
190 StringBuilder builder = new StringBuilder();
191 builder.append("view");
192 builder.append("?faces-redirect=true");
193 builder.append("&");
194 builder.append(settings.getViewParameterName());
195 builder.append("=");
196 builder.append(state.getId());
197
198 return builder.toString();
199 }
200
201
202
203
204
205
206 protected void convertFiles(ReportFile parent) throws IOException,
207 ConfigurationException {
208 List<ReportFile> files = repository.getFiles(parent, fileFilter);
209
210 for (ReportFile file : files) {
211 if (file.isDirectory()) {
212 convertFiles(file);
213 } else {
214 String error = null;
215
216 ReportFile result = null;
217
218 FacesContext context = FacesContext.getCurrentInstance();
219 ResourceBundle bundle = context.getApplication()
220 .getResourceBundle(context, "plugin_msg");
221
222 try {
223 ViewState state = convertFile(file);
224
225 String name = file.getName().substring(
226 0,
227 file.getName().length()
228 - file.getExtension().length())
229 + "pivot4j";
230
231 result = repository.createFile(parent, name,
232 new ReportContent(state));
233 } catch (InvalidConnectionInfoException e) {
234 error = bundle
235 .getString("message.migrate.error.dataSource");
236 } catch (Exception e) {
237 error = bundle.getString("message.migrate.error") + e;
238
239 if (log.isErrorEnabled()) {
240 log.error(error, e);
241 }
242 }
243
244 convertedFiles.add(new Entry(file, result, error));
245 }
246 }
247 }
248
249
250
251
252
253
254
255 protected ViewState convertFile(ReportFile file)
256 throws InvalidConnectionInfoException, ConfigurationException,
257 IOException {
258 if (log.isDebugEnabled()) {
259 log.debug("Migrating JPivot report : " + file);
260 }
261
262 InputStream in = null;
263
264 try {
265 in = repository.readContent(file);
266
267 XMLConfiguration config = new XMLConfiguration();
268 config.setRootElementName("action-sequence");
269 config.setDelimiterParsingDisabled(true);
270 config.load(in);
271
272 return convertFile(config);
273 } finally {
274 IOUtils.closeQuietly(in);
275 }
276 }
277
278
279
280
281
282
283 protected ViewState convertFile(HierarchicalConfiguration config)
284 throws InvalidConnectionInfoException {
285 HierarchicalConfiguration report = config
286 .configurationAt("actions.action-definition.component-definition");
287
288 String title = report.getString("title");
289 String query = report.getString("query");
290 String cube = report.getString("cube");
291 String catalog = report.getString("model");
292
293 if (log.isDebugEnabled()) {
294 log.debug(" - title : " + title);
295 log.debug(" - query : " + query);
296 log.debug(" - cube : " + cube);
297 log.debug(" - catalog : " + catalog);
298 }
299
300 if (cube == null) {
301
302
303
304
305
306 String originalCubeName = cube;
307 String originalCatalogName = catalog;
308
309 if (log.isWarnEnabled()) {
310 log.warn("Cube name is not specified. Trying to guess the correct cube and catalog names.");
311 }
312
313 MdxParser parser = new MdxParserImpl();
314 MdxStatement statement = parser.parse(query);
315
316 cube = statement.getCube().getNames().get(0).getUnquotedName();
317
318 if (log.isInfoEnabled()) {
319 log.info("Cube name specified in MDX query : " + cube);
320 }
321
322 List<CubeInfo> cubes = dataSourceManager.getCubes(catalog);
323
324 boolean found = false;
325
326 for (CubeInfo cubeInfo : cubes) {
327 found = cubeInfo.getName().equalsIgnoreCase(cube);
328 if (found) {
329 cube = cubeInfo.getName();
330 break;
331 }
332 }
333
334 if (!found) {
335 throw new InvalidConnectionInfoException(originalCubeName,
336 originalCatalogName);
337 }
338 }
339
340 ConnectionInfo connectionInfo = new ConnectionInfo(catalog, cube);
341
342 OlapDataSource dataSource = dataSourceManager
343 .getDataSource(connectionInfo);
344
345 PivotModel model = new PivotModelImpl(dataSource);
346 model.setMdx(query);
347
348 TableRenderer renderer = new TableRenderer();
349
350
351
352
353
354 ViewState state = viewStateHolder
355 .createNewState(connectionInfo, viewId);
356
357 state.setName(title);
358 state.setConnectionInfo(connectionInfo);
359 state.setModel(model);
360 state.setRendererState(renderer.saveState());
361
362 return state;
363 }
364
365
366
367
368 public String getViewId() {
369 return viewId;
370 }
371
372
373
374
375
376 public void setViewId(String viewId) {
377 this.viewId = viewId;
378 }
379
380
381
382
383 public ReportRepository getRepository() {
384 return repository;
385 }
386
387
388
389
390
391 public void setRepository(ReportRepository repository) {
392 this.repository = repository;
393 }
394
395
396
397
398 public DataSourceManager getDataSourceManager() {
399 return dataSourceManager;
400 }
401
402
403
404
405
406 public void setDataSourceManager(DataSourceManager dataSourceManager) {
407 this.dataSourceManager = dataSourceManager;
408 }
409
410
411
412
413 public Settings getSettings() {
414 return settings;
415 }
416
417
418
419
420
421 public void setSettings(Settings settings) {
422 this.settings = settings;
423 }
424
425
426
427
428 public ViewStateHolder getViewStateHolder() {
429 return viewStateHolder;
430 }
431
432
433
434
435
436 public void setViewStateHolder(ViewStateHolder viewStateHolder) {
437 this.viewStateHolder = viewStateHolder;
438 }
439
440
441
442
443 public TreeNode getRootNode() {
444 if (rootNode == null) {
445 this.rootNode = new DefaultTreeNode();
446 rootNode.setExpanded(true);
447
448 RepositoryNode node;
449
450 try {
451 node = new RepositoryNode(repository.getRoot(), repository);
452 } catch (IOException e) {
453 throw new FacesException(e);
454 }
455
456 node.setExpanded(true);
457 node.setFilter(fileFilter);
458
459 rootNode.getChildren().add(node);
460 }
461
462 return rootNode;
463 }
464
465 protected RepositoryNode getRepositoryRootNode() {
466 return (RepositoryNode) getRootNode().getChildren().get(0);
467 }
468
469 public TreeNode getSelection() {
470 return selection;
471 }
472
473 public void setSelection(TreeNode selection) {
474 this.selection = selection;
475 }
476
477
478
479
480 public List<Entry> getConvertedFiles() {
481 return convertedFiles;
482 }
483
484
485
486
487 public Entry getSelectedFile() {
488 return selectedFile;
489 }
490
491
492
493
494
495 public void setSelectedFile(Entry selectedFile) {
496 this.selectedFile = selectedFile;
497 }
498
499
500
501
502 public boolean isMigrationDone() {
503 return migrationDone;
504 }
505
506 static class MigrationTargetFilter implements RepositoryFileFilter,
507 Serializable {
508
509 private static final long serialVersionUID = 4427077075329175626L;
510
511
512
513
514 @Override
515 public boolean accept(ReportFile file) {
516 if (file.isDirectory()) {
517 return !file.getPath().startsWith(ReportFile.SEPARATOR + "etc");
518 } else {
519 return "xaction".equalsIgnoreCase(file.getExtension())
520 || "xjpivot".equalsIgnoreCase(file.getExtension());
521 }
522 }
523 }
524
525 static class InvalidConnectionInfoException extends Exception {
526
527 private static final long serialVersionUID = 8066830074992740616L;
528
529 private String cube;
530
531 private String catalog;
532
533
534
535
536
537 private InvalidConnectionInfoException(String cube, String catalog) {
538 this.cube = cube;
539 this.catalog = catalog;
540 }
541
542
543
544
545 public String getCube() {
546 return cube;
547 }
548
549
550
551
552 public String getCatalog() {
553 return catalog;
554 }
555 }
556
557 public static class Entry {
558
559 private ReportFile file;
560
561 private ReportFile result;
562
563 private String error;
564
565
566
567
568
569
570 private Entry(ReportFile file, ReportFile result, String error) {
571 this.file = file;
572 this.error = error;
573 this.result = result;
574 }
575
576
577
578
579 public ReportFile getFile() {
580 return file;
581 }
582
583
584
585
586 public ReportFile getResult() {
587 return result;
588 }
589
590
591
592
593 public String getError() {
594 return error;
595 }
596 }
597 }