1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.springframework.extensions.webscripts;
20
21 import java.io.ByteArrayInputStream;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.InputStreamReader;
25 import java.io.Reader;
26 import java.io.StringReader;
27 import java.util.ArrayList;
28 import java.util.HashMap;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.StringTokenizer;
32
33 import org.apache.commons.logging.Log;
34 import org.apache.commons.logging.LogFactory;
35 import org.springframework.extensions.surf.exception.ConnectorProviderException;
36 import org.springframework.extensions.surf.exception.RemoteConfigException;
37 import org.springframework.extensions.surf.exception.WebScriptsPlatformException;
38 import org.springframework.extensions.surf.util.URLEncoder;
39 import org.springframework.extensions.webscripts.connector.Connector;
40 import org.springframework.extensions.webscripts.connector.ConnectorContext;
41 import org.springframework.extensions.webscripts.connector.ConnectorProvider;
42 import org.springframework.extensions.webscripts.connector.ConnectorProviderImpl;
43 import org.springframework.extensions.webscripts.connector.ConnectorService;
44 import org.springframework.extensions.webscripts.connector.HttpMethod;
45 import org.springframework.extensions.webscripts.connector.Response;
46
47 import freemarker.cache.TemplateLoader;
48
49
50
51
52
53
54
55
56
57 public class RemoteStore extends AbstractStore
58 {
59 private static Log logger = LogFactory.getLog(RemoteStore.class);
60
61 public static final String DEFAULT_API = "/remotestore";
62 public static final String DEFAULT_ENDPOINT_ID = "alfresco";
63
64 private static final String API_LISTPATTERN = "listpattern";
65 private static final String API_LISTALL = "listall";
66 private static final String API_GET = "get";
67 private static final String API_CREATE = "create";
68 private static final String API_DELETE = "delete";
69 private static final String API_UPDATE = "update";
70 private static final String API_LASTMODIFIED = "lastmodified";
71 private static final String API_HAS = "has";
72
73 private ConnectorService connectorService;
74 private ConnectorProvider connectorProvider;
75
76 private String storeId;
77 private String endpoint;
78 private String path;
79 private String api;
80 private String webappId;
81 private String webappPathPrefix;
82
83
84
85
86 public void setConnectorService(ConnectorService service)
87 {
88 this.connectorService = service;
89 }
90
91
92
93
94
95
96 public ConnectorService getConnectorService()
97 {
98 return this.connectorService;
99 }
100
101
102
103
104 public void setConnectorProvider(ConnectorProvider connectorProvider)
105 {
106 this.connectorProvider = connectorProvider;
107 }
108
109
110
111
112 public ConnectorProvider getConnectorProvider()
113 {
114 return this.connectorProvider;
115 }
116
117
118
119
120 public void setApi(String api)
121 {
122 this.api = api;
123 }
124
125
126
127
128
129
130 public String getApi()
131 {
132 return this.api;
133 }
134
135
136
137
138
139
140 public void setPath(String path)
141 {
142 this.path = path;
143 }
144
145
146
147
148 public void setEndpoint(String endpoint)
149 {
150 this.endpoint = endpoint;
151 }
152
153
154
155
156
157
158 public String getEndpoint()
159 {
160 return this.endpoint;
161 }
162
163
164
165
166
167
168
169 public void setWebappId(String webappId)
170 {
171 this.webappId = webappId;
172 }
173
174 public String getWebappPathPrefix()
175 {
176 return this.webappPathPrefix;
177 }
178
179 public void setWebappPathPrefix(String webappPathPrefix)
180 {
181 this.webappPathPrefix = webappPathPrefix;
182 }
183
184
185
186
187
188
189
190 public String getWebappId()
191 {
192 String value = this.webappId;
193
194 if (value == null && this.getPreviewContext() != null)
195 {
196 value = getPreviewContext().getWebappId();
197 }
198
199 return value;
200 }
201
202
203
204
205
206
207
208
209
210 public void setStoreId(String storeId)
211 {
212 this.storeId = storeId;
213 }
214
215
216
217
218
219
220 public String getStoreId()
221 {
222 String value = this.storeId;
223
224 if (value == null && getPreviewContext() != null)
225 {
226 value = getPreviewContext().getStoreId();
227 }
228
229 return value;
230 }
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251 public String getStorePath()
252 {
253 String value = this.path;
254
255
256
257
258 if (this.getStoreId() != null && this.getWebappId() != null)
259 {
260 value = this.getWebappPathPrefix();
261 if (value != null)
262 {
263 if (!value.endsWith("/"))
264 {
265 value += "/";
266 }
267 if (this.path.startsWith("/"))
268 {
269 value += this.path.substring(1);
270 }
271 else
272 {
273 value += this.path;
274 }
275 }
276 }
277
278 if (value == null)
279 {
280 value = "";
281 }
282
283 return value;
284 }
285
286
287
288
289 public void init()
290 {
291 if (this.connectorService == null)
292 {
293 throw new IllegalArgumentException("ConnectorService reference is mandatory for RemoteStore.");
294 }
295 if (this.getEndpoint() == null || getEndpoint().length() == 0)
296 {
297 throw new IllegalArgumentException("Endpoint ID is mandatory for RemoteStore.");
298 }
299 if (this.getApi() == null || this.getApi().length() == 0)
300 {
301 throw new IllegalArgumentException("API name is mandatory for RemoteStore.");
302 }
303 if (this.getStorePath() == null)
304 {
305 throw new IllegalArgumentException("Path prefix is mandatory for RemoteStore.");
306 }
307
308 if (logger.isDebugEnabled())
309 {
310 logger.debug("RemoteStore initialised with endpoint id '" + this.getEndpoint() + "' API path '" +
311 this.getApi() + "' path prefix '" + this.getStorePath() + "'.");
312 }
313 }
314
315
316
317
318 public boolean isSecure()
319 {
320 return false;
321 }
322
323
324
325
326 public boolean exists()
327 {
328
329
330 return true;
331 }
332
333
334
335
336 public boolean hasDocument(String documentPath) throws IOException
337 {
338 boolean hasDocument = false;
339
340 Response res = callGet(buildEncodeCall(API_HAS, documentPath));
341 if (Status.STATUS_OK == res.getStatus().getCode())
342 {
343 hasDocument = Boolean.parseBoolean(res.getResponse());
344 }
345 else
346 {
347 throw new IOException("Unable to test document path: " + documentPath +
348 " in remote store: " + this.getEndpoint() +
349 " due to error: " + res.getStatus().getCode() + " " + res.getStatus().getMessage());
350 }
351
352 if (logger.isDebugEnabled())
353 logger.debug("RemoteStore.hasDocument() " + documentPath + " = " + hasDocument);
354
355 return hasDocument;
356 }
357
358
359
360
361 public long lastModified(String documentPath) throws IOException
362 {
363 Response res = callGet(buildEncodeCall(API_LASTMODIFIED, documentPath));
364 if (Status.STATUS_OK == res.getStatus().getCode())
365 {
366 try
367 {
368 long lastMod = Long.parseLong(res.getResponse());
369
370 if (logger.isDebugEnabled())
371 logger.debug("RemoteStore.lastModified() " + documentPath + " = " + lastMod);
372
373 return lastMod;
374 }
375 catch (NumberFormatException ne)
376 {
377 throw new IOException("Failed to process lastModified response: " + ne.getMessage());
378 }
379 }
380 else
381 {
382 throw new IOException("Unable to get lastModified date of document path: " + documentPath +
383 " in remote store: " + this.getEndpoint() +
384 " due to error: " + res.getStatus().getCode() + " " + res.getStatus().getMessage());
385 }
386 }
387
388
389
390
391 public void updateDocument(String documentPath, String content) throws IOException
392 {
393 ByteArrayInputStream in = new ByteArrayInputStream(content.getBytes("UTF-8"));
394 Response res = callPost(buildEncodeCall(API_UPDATE, documentPath), in);
395
396 if (logger.isDebugEnabled())
397 logger.debug("RemoteStore.updateDocument() " + documentPath + " = " + res.getStatus().getCode());
398
399 if (Status.STATUS_OK != res.getStatus().getCode())
400 {
401 throw new IOException("Unable to update document path: " + documentPath +
402 " in remote store: " + this.getEndpoint() +
403 " due to error: " + res.getStatus().getCode() + " " + res.getStatus().getMessage());
404 }
405 }
406
407
408
409
410 public boolean removeDocument(String documentPath) throws IOException
411 {
412 Response res = callDelete(buildEncodeCall(API_DELETE, documentPath));
413
414 boolean removed = (Status.STATUS_OK == res.getStatus().getCode());
415
416 if (logger.isDebugEnabled())
417 logger.debug("RemoteStore.removeDocument() " + documentPath + " = " + res.getStatus().getCode() + " (removed = "+removed+")");
418
419 return removed;
420 }
421
422
423
424
425 public void createDocument(String documentPath, String content) throws IOException
426 {
427 ByteArrayInputStream in = new ByteArrayInputStream(content.getBytes("UTF-8"));
428 Response res = callPost(buildEncodeCall(API_CREATE, documentPath), in);
429
430 if (logger.isDebugEnabled())
431 logger.debug("RemoteStore.createDocument() " + documentPath + " = " + res.getStatus().getCode());
432
433 if (Status.STATUS_OK != res.getStatus().getCode())
434 {
435 throw new IOException("Unable to create document path: " + documentPath +
436 " in remote store: " + this.getEndpoint() +
437 " due to error: " + res.getStatus().getCode() + " " + res.getStatus().getMessage());
438 }
439 }
440
441
442
443
444 public InputStream getDocument(String documentPath) throws IOException
445 {
446 return getDocumentResponse(documentPath).getResponseStream();
447 }
448
449 private Response getDocumentResponse(String path)
450 throws IOException
451 {
452 Response res = callGet(buildEncodeCall(API_GET, path));
453
454 if (logger.isDebugEnabled())
455 logger.debug("RemoteStore.getDocument() " + path + " = " + res.getStatus().getCode());
456
457 if (Status.STATUS_OK == res.getStatus().getCode())
458 {
459 return res;
460 }
461 else
462 {
463 throw new IOException("Unable to retrieve document path: " + path +
464 " in remote store: " + this.getEndpoint() +
465 " due to error: " + res.getStatus().getCode() + " " + res.getStatus().getMessage());
466 }
467 }
468
469
470
471
472 public String[] getAllDocumentPaths()
473 {
474 Response res = callGet(buildEncodeCall(API_LISTALL, ""));
475
476 if (logger.isDebugEnabled())
477 logger.debug("RemoteStore.getAllDocumentPaths() " + res.getStatus().getCode());
478
479 if (Status.STATUS_OK == res.getStatus().getCode())
480 {
481
482 List<String> list = new ArrayList<String>(128);
483 StringTokenizer t = new StringTokenizer(res.getResponse(), "\n");
484 while (t.hasMoreTokens())
485 {
486 list.add(t.nextToken());
487 }
488 String[] paths = list.toArray(new String[list.size()]);
489
490
491
492 convertToRelativePaths(paths);
493
494 return paths;
495 }
496 else
497 {
498 return new String[0];
499 }
500 }
501
502
503
504
505 public String[] getDocumentPaths(String path, boolean includeSubPaths, String documentPattern)
506 {
507 Map<String, String> args = new HashMap<String, String>(1, 1.0f);
508 args.put("m", documentPattern);
509 Response res = callGet(buildEncodeCall(API_LISTPATTERN, path, args));
510
511 if (logger.isDebugEnabled())
512 logger.debug("RemoteStore.getDocumentPaths() " + path + " subpaths: " + includeSubPaths +
513 " pattern: " + documentPattern + " = " + res.getStatus().getCode() + " " + res.getStatus().getMessage());
514
515 if (Status.STATUS_OK == res.getStatus().getCode())
516 {
517
518 List<String> list = new ArrayList<String>(128);
519 StringTokenizer t = new StringTokenizer(res.getResponse(), "\n");
520 while (t.hasMoreTokens())
521 {
522 list.add(t.nextToken());
523 }
524 String[] paths = list.toArray(new String[list.size()]);
525
526
527
528 convertToRelativePaths(paths);
529
530 return paths;
531 }
532 else
533 {
534 return new String[0];
535 }
536 }
537
538
539
540
541 public String[] getDescriptionDocumentPaths()
542 {
543 return getDocumentPaths("", true, "*.desc.xml");
544 }
545
546
547
548
549 public String[] getScriptDocumentPaths(WebScript script)
550 {
551 String scriptPaths = script.getDescription().getId() + ".*";
552 return getDocumentPaths("", false, scriptPaths);
553 }
554
555
556
557
558 public ScriptLoader getScriptLoader()
559 {
560 return new RemoteStoreScriptLoader();
561 }
562
563
564
565
566 public TemplateLoader getTemplateLoader()
567 {
568 return new RemoteStoreTemplateLoader();
569 }
570
571
572
573
574 public String getBasePath()
575 {
576 return getStorePath();
577 }
578
579
580
581
582
583
584
585
586
587
588 private String buildEncodeCall(String method, String documentPath)
589 {
590 return buildEncodeCall(method, documentPath, null);
591 }
592
593
594
595
596
597
598
599
600
601
602 private String buildEncodeCall(String method, String documentPath, Map<String, String> args)
603 {
604 StringBuilder buf = new StringBuilder(128);
605
606 buf.append(this.getApi());
607 buf.append('/');
608 buf.append(method);
609
610
611 String fullPath = this.getStorePath() + "/" + documentPath;
612 for (StringTokenizer t = new StringTokenizer(fullPath, "/"); t.hasMoreTokens();
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660 private Response callPost(String uri, InputStream in)
661 {
662 try
663 {
664 Connector con = getConnector();
665 return con.call(uri, null, in);
666 }
667 catch (ConnectorProviderException cpe)
668 {
669 throw new WebScriptsPlatformException("Unable to find config for remote store.", cpe);
670 }
671 }
672
673
674
675
676 private Response callGet(String uri)
677 {
678 try
679 {
680 Connector con = getConnector();
681 return con.call(uri);
682 }
683 catch (ConnectorProviderException cpe)
684 {
685 throw new WebScriptsPlatformException("Unable to find config for remote store.", cpe);
686 }
687 }
688
689
690
691
692 private Response callDelete(String uri)
693 {
694 try
695 {
696 Connector con = getConnector();
697 ConnectorContext context = new ConnectorContext(HttpMethod.DELETE, null, null);
698 return con.call(uri, context);
699 }
700 catch (ConnectorProviderException cpe)
701 {
702 throw new WebScriptsPlatformException("Unable to find config for remote store.", cpe);
703 }
704 }
705
706
707
708
709
710
711
712
713
714
715 private Connector getConnector() throws ConnectorProviderException
716 {
717 Connector conn = null;
718
719
720 if (connectorProvider == null)
721 {
722 connectorProvider = new ConnectorProviderImpl();
723 }
724
725
726 conn = getConnectorProvider().provide(this.getEndpoint());
727
728 return conn;
729 }
730
731
732
733
734
735
736
737 protected class RemoteStoreScriptLoader implements ScriptLoader
738 {
739
740
741
742 public ScriptContent getScript(String path)
743 {
744 ScriptContent sc = null;
745 try
746 {
747 if (hasDocument(path))
748 {
749 sc = new RemoteScriptContent(path);
750 }
751 }
752 catch (IOException e)
753 {
754 throw new WebScriptException("Error locating script " + path, e);
755 }
756 return sc;
757 }
758 }
759
760
761
762
763
764
765
766 private class RemoteStoreTemplateLoader implements TemplateLoader
767 {
768
769
770
771 public void closeTemplateSource(Object templateSource) throws IOException
772 {
773
774 }
775
776
777
778
779 public Object findTemplateSource(String name) throws IOException
780 {
781 RemoteStoreTemplateSource source = null;
782 if (hasDocument(name))
783 {
784 source = new RemoteStoreTemplateSource(name);
785 }
786 return source;
787 }
788
789
790
791
792 public long getLastModified(Object templateSource)
793 {
794 return ((RemoteStoreTemplateSource)templateSource).lastModified();
795 }
796
797
798
799
800 public Reader getReader(Object templateSource, String encoding) throws IOException
801 {
802 return ((RemoteStoreTemplateSource)templateSource).getReader(encoding);
803 }
804 }
805
806
807
808
809
810
811
812
813
814 private class RemoteStoreTemplateSource
815 {
816 private String templatePath;
817
818 private RemoteStoreTemplateSource(String path)
819 {
820 this.templatePath = path;
821 }
822
823 private long lastModified()
824 {
825 try
826 {
827 return RemoteStore.this.lastModified(templatePath);
828 }
829 catch (IOException e)
830 {
831 return -1;
832 }
833 }
834
835 private Reader getReader(String encoding)
836 throws IOException
837 {
838 Response res = getDocumentResponse(templatePath);
839 if (encoding == null || encoding.equals(res.getEncoding()))
840 {
841 return new StringReader(res.getResponse());
842 }
843 else
844 {
845 return new InputStreamReader(res.getResponseStream(), encoding);
846 }
847 }
848 }
849
850
851
852
853
854
855
856
857
858 private class RemoteScriptContent implements ScriptContent
859 {
860 private String scriptPath;
861
862
863
864
865
866
867 private RemoteScriptContent(String path)
868 {
869 this.scriptPath = path;
870 }
871
872
873
874
875 public String getPath()
876 {
877 return getStorePath() + '/' + this.scriptPath;
878 }
879
880
881
882
883 public String getPathDescription()
884 {
885 return getStorePath() + '/' + this.scriptPath + " loaded from endpoint: " + getEndpoint();
886 }
887
888
889
890
891 public InputStream getInputStream()
892 {
893 try
894 {
895 return getDocumentResponse(scriptPath).getResponseStream();
896 }
897 catch (IOException e)
898 {
899 throw new WebScriptsPlatformException("Unable to load script: " + scriptPath, e);
900 }
901 }
902
903
904
905
906 public Reader getReader()
907 {
908 try
909 {
910 Response res = getDocumentResponse(scriptPath);
911 if (res.getEncoding() != null)
912 {
913 return new InputStreamReader(res.getResponseStream(), res.getEncoding());
914 }
915 else
916 {
917 return new InputStreamReader(res.getResponseStream());
918 }
919 }
920 catch (IOException e)
921 {
922 throw new WebScriptsPlatformException("Unable to load script: " + scriptPath, e);
923 }
924 }
925
926
927
928
929 public boolean isCachable()
930 {
931 return false;
932 }
933
934
935
936
937 public boolean isSecure()
938 {
939 return false;
940 }
941 }
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975 private void convertToRelativePaths(String[] fullPaths)
976 {
977
978
979
980
981
982
983
984
985 String truncationString = "";
986 if (this.getStoreId() != null && this.getWebappId() != null)
987 {
988
989 truncationString = "www/avm_webapps/" + this.getWebappId();
990 if (this.getStorePath() != null && this.getStorePath().length() > 0)
991 {
992 truncationString += this.getStorePath();
993 }
994 }
995
996
997 if (truncationString != null && truncationString.length() > 0)
998 {
999
1000 for (int i = 0; i < fullPaths.length; i++)
1001 {
1002
1003
1004 String fullPath = fullPaths[i];
1005
1006 int x = fullPath.indexOf(truncationString);
1007 if (x != -1)
1008 {
1009 fullPath = fullPath.substring(x + truncationString.length());
1010 }
1011
1012 if (!fullPath.startsWith("/"))
1013 {
1014 fullPath = "/" + fullPath;
1015 }
1016
1017 fullPaths[i] = fullPath;
1018 }
1019 }
1020 }
1021 }