PageRenderTime 51ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/projects/rssowl-2.0.5/org.rssowl.ui/src/org/rssowl/ui/internal/ApplicationServer.java

https://gitlab.com/essere.lab.public/qualitas.class-corpus
Java | 783 lines | 458 code | 126 blank | 199 comment | 112 complexity | fd835d22119d0d8c2498c6cbd0d824b7 MD5 | raw file
  1. /* ********************************************************************** **
  2. ** Copyright notice **
  3. ** **
  4. ** (c) 2005-2009 RSSOwl Development Team **
  5. ** http://www.rssowl.org/ **
  6. ** **
  7. ** All rights reserved **
  8. ** **
  9. ** This program and the accompanying materials are made available under **
  10. ** the terms of the Eclipse Public License v1.0 which accompanies this **
  11. ** distribution, and is available at: **
  12. ** http://www.rssowl.org/legal/epl-v10.html **
  13. ** **
  14. ** A copy is found in the file epl-v10.html and important notices to the **
  15. ** license from the team is found in the textfile LICENSE.txt distributed **
  16. ** in this package. **
  17. ** **
  18. ** This copyright notice MUST APPEAR in all copies of the file! **
  19. ** **
  20. ** Contributors: **
  21. ** RSSOwl Development Team - initial API and implementation **
  22. ** **
  23. ** ********************************************************************** */
  24. package org.rssowl.ui.internal;
  25. import org.eclipse.core.runtime.IProgressMonitor;
  26. import org.eclipse.core.runtime.IStatus;
  27. import org.eclipse.core.runtime.SafeRunner;
  28. import org.eclipse.core.runtime.Status;
  29. import org.eclipse.core.runtime.jobs.Job;
  30. import org.eclipse.jface.viewers.ContentViewer;
  31. import org.eclipse.jface.viewers.IStructuredContentProvider;
  32. import org.rssowl.core.persist.IBookMark;
  33. import org.rssowl.core.persist.IEntity;
  34. import org.rssowl.core.persist.INews;
  35. import org.rssowl.core.persist.INewsBin;
  36. import org.rssowl.core.persist.ISearchMark;
  37. import org.rssowl.core.persist.reference.BookMarkReference;
  38. import org.rssowl.core.persist.reference.FolderReference;
  39. import org.rssowl.core.persist.reference.ModelReference;
  40. import org.rssowl.core.persist.reference.NewsBinReference;
  41. import org.rssowl.core.persist.reference.NewsReference;
  42. import org.rssowl.core.persist.reference.SearchMarkReference;
  43. import org.rssowl.core.util.CoreUtils;
  44. import org.rssowl.core.util.LoggingSafeRunnable;
  45. import org.rssowl.core.util.StringUtils;
  46. import org.rssowl.core.util.URIUtils;
  47. import org.rssowl.ui.internal.FolderNewsMark.FolderNewsMarkReference;
  48. import org.rssowl.ui.internal.editors.feed.NewsBrowserLabelProvider;
  49. import org.rssowl.ui.internal.editors.feed.NewsBrowserViewer;
  50. import java.io.BufferedOutputStream;
  51. import java.io.BufferedReader;
  52. import java.io.BufferedWriter;
  53. import java.io.IOException;
  54. import java.io.InputStreamReader;
  55. import java.io.OutputStreamWriter;
  56. import java.net.BindException;
  57. import java.net.InetAddress;
  58. import java.net.ServerSocket;
  59. import java.net.Socket;
  60. import java.net.UnknownHostException;
  61. import java.text.DateFormat;
  62. import java.text.SimpleDateFormat;
  63. import java.util.ArrayList;
  64. import java.util.Arrays;
  65. import java.util.Date;
  66. import java.util.List;
  67. import java.util.Locale;
  68. import java.util.Map;
  69. import java.util.StringTokenizer;
  70. import java.util.concurrent.ConcurrentHashMap;
  71. /**
  72. * The <code>NewsServer</code> is a Singleton that serves HTML for a request of
  73. * News. A Browser can navigate to a local URL with Port 8795 and use some
  74. * special parameters to request either a List of News or complete Feeds.
  75. * <p>
  76. * TODO As more and more stuff is handled by this server, it should be
  77. * considered to make it extensible by allowing to register handlers for certain
  78. * operations.
  79. * </p>
  80. *
  81. * @author bpasero
  82. */
  83. public class ApplicationServer {
  84. /* The Singleton Instance */
  85. private static ApplicationServer fgInstance = new ApplicationServer();
  86. /* Local URL Default Values */
  87. static final String PROTOCOL = "http"; //$NON-NLS-1$
  88. static final String DEFAULT_LOCALHOST = "127.0.0.1"; //$NON-NLS-1$
  89. @SuppressWarnings("all")
  90. static final int DEFAULT_SOCKET_PORT = Application.IS_ECLIPSE ? 8775 : 8795;
  91. /* Local URL Parts */
  92. static String LOCALHOST = DEFAULT_LOCALHOST;
  93. static int SOCKET_PORT = DEFAULT_SOCKET_PORT;
  94. /* Handshake Message */
  95. static final String STARTUP_HANDSHAKE = "org.rssowl.ui.internal.StartupHandshake"; //$NON-NLS-1$
  96. /* DWord controlling the startup-handshake */
  97. private static final String MULTI_INSTANCE_PROPERTY = "multiInstance"; //$NON-NLS-1$
  98. /* DWord controlling the localhost value */
  99. private static final String LOCALHOST_PROPERTY = "localhost"; //$NON-NLS-1$
  100. /* DWord controlling the port value */
  101. private static final String PORT_PROPERTY = "port"; //$NON-NLS-1$
  102. /* Identifies the Viewer providing the Content */
  103. private static final String ID = "id="; //$NON-NLS-1$
  104. /* Used after all HTTP-Headers */
  105. private static final String CRLF = "\r\n"; //$NON-NLS-1$
  106. /* Registry of known Viewer */
  107. private static Map<String, ContentViewer> fRegistry = new ConcurrentHashMap<String, ContentViewer>();
  108. /* Supported Operations */
  109. private static final String OP_DISPLAY_FOLDER = "displayFolder="; //$NON-NLS-1$
  110. private static final String OP_DISPLAY_BOOKMARK = "displayBookMark="; //$NON-NLS-1$
  111. private static final String OP_DISPLAY_NEWSBIN = "displayNewsBin="; //$NON-NLS-1$
  112. private static final String OP_DISPLAY_SEARCHMARK = "displaySearchMark="; //$NON-NLS-1$
  113. private static final String OP_DISPLAY_NEWS = "displayNews="; //$NON-NLS-1$
  114. private static final String OP_RESOURCE = "resource="; //$NON-NLS-1$
  115. /* Windows only: Mark of the Web */
  116. private static final String IE_MOTW = "<!-- saved from url=(0014)about:internet -->"; //$NON-NLS-1$
  117. /* RFC 1123 Date Format for the respond header */
  118. private static final DateFormat RFC_1123_DATE = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.ENGLISH); //$NON-NLS-1$
  119. /* Interface used to handle a startup-handshake */
  120. static interface HandshakeHandler {
  121. /**
  122. * Handler for the hand-shake done on startup in case the application server
  123. * was found already running by another RSSOwl process.
  124. *
  125. * @param token A message to pass via hand-shake.
  126. */
  127. void handle(String token);
  128. }
  129. private ServerSocket fSocket;
  130. private Job fServerJob;
  131. private int fPort;
  132. private HandshakeHandler fHandshakeHandler;
  133. /**
  134. * Returns the singleton instance of the ApplicationServer.
  135. *
  136. * @return the singleton instance of the ApplicationServer.
  137. */
  138. public static ApplicationServer getDefault() {
  139. return fgInstance;
  140. }
  141. /**
  142. * Attempts to start the server. Will throw an IOException in case of a
  143. * problem.
  144. *
  145. * @throws IOException in case of a problem starting the server.
  146. * @throws UnknownHostException in case the host is unknown.
  147. * @throws BindException in case of a failure binding the server to a port.
  148. */
  149. public void startup() throws IOException {
  150. /* Server already running */
  151. if (isRunning())
  152. return;
  153. /* Determine Localhost Value */
  154. String localhostProperty = System.getProperty(LOCALHOST_PROPERTY);
  155. if (localhostProperty != null && localhostProperty.length() > 0)
  156. LOCALHOST = localhostProperty;
  157. /* Determine Port Value */
  158. String portProperty = System.getProperty(PORT_PROPERTY);
  159. if (portProperty != null && portProperty.length() > 0) {
  160. try {
  161. SOCKET_PORT = Integer.parseInt(portProperty);
  162. } catch (NumberFormatException e) {
  163. Activator.getDefault().logError(e.getMessage(), e);
  164. }
  165. }
  166. /* Server not yet running */
  167. boolean usePortRange = Application.IS_ECLIPSE || System.getProperty(MULTI_INSTANCE_PROPERTY) != null;
  168. fSocket = createServerSocket(usePortRange);
  169. if (fSocket != null)
  170. listen();
  171. }
  172. /** Stop the Application Server */
  173. public void shutdown() {
  174. fServerJob.cancel();
  175. try {
  176. if (fSocket != null)
  177. fSocket.close();
  178. } catch (IOException e) {
  179. if (Activator.getDefault() != null)
  180. Activator.getDefault().logError(e.getMessage(), e);
  181. }
  182. }
  183. /**
  184. * Check if the server is running or not.
  185. *
  186. * @return <code>TRUE</code> in case the server is running and
  187. * <code>FALSE</code> otherwise.
  188. */
  189. public boolean isRunning() {
  190. return fSocket != null;
  191. }
  192. /* Registers the Handler for Hand-Shaking on startup */
  193. void setHandshakeHandler(HandshakeHandler handler) {
  194. fHandshakeHandler = handler;
  195. }
  196. /* Attempt to create Server-Socket with retry-option */
  197. private ServerSocket createServerSocket(boolean usePortRange) throws IOException {
  198. /* Ports to try */
  199. List<Integer> ports = new ArrayList<Integer>();
  200. ports.add(SOCKET_PORT);
  201. /* Try up to 10 different ports if set */
  202. if (usePortRange) {
  203. for (int i = 1; i < 10; i++)
  204. ports.add(SOCKET_PORT + i);
  205. }
  206. /* Attempt to open Port */
  207. for (int i = 0; i < ports.size(); i++) {
  208. try {
  209. int port = ports.get(i);
  210. fPort = port;
  211. return new ServerSocket(fPort, 50, InetAddress.getByName(LOCALHOST));
  212. } catch (UnknownHostException e) {
  213. throw e;
  214. } catch (BindException e) {
  215. if (i == (ports.size() - 1))
  216. throw e;
  217. }
  218. }
  219. return null;
  220. }
  221. /**
  222. * Returns <code>TRUE</code> if the given URL is a local NewsServer URL.
  223. *
  224. * @param url The URL to check for being a NewsServer URL.
  225. * @return <code>TRUE</code> if the given URL is a local NewsServer URL.
  226. */
  227. public boolean isNewsServerUrl(String url) {
  228. return url.contains(LOCALHOST) && url.contains(String.valueOf(fPort));
  229. }
  230. /**
  231. * Registers a Viewer under a certain ID to the Registry. Viewers need to
  232. * register if they want to use the Server. Based on the ID, the Server is
  233. * asking the correct Viewer for the Content.
  234. *
  235. * @param id The unique ID under which the Viewer is stored in the registry.
  236. * @param viewer The Viewer to store in the registry.
  237. */
  238. public void register(String id, ContentViewer viewer) {
  239. fRegistry.put(id, viewer);
  240. }
  241. /**
  242. * Removes a Viewer from the registry.
  243. *
  244. * @param id The ID of the Viewer to remove from the registry.
  245. */
  246. public void unregister(String id) {
  247. fRegistry.remove(id);
  248. }
  249. /**
  250. * Check wether the given URL contains one of the display-operations of this
  251. * Server.
  252. *
  253. * @param url The URL to Test for a Display Operation.
  254. * @return Returns <code>TRUE</code> if the given URL is a display-operation.
  255. */
  256. public boolean isDisplayOperation(String url) {
  257. if (!StringUtils.isSet(url))
  258. return false;
  259. return url.contains(OP_DISPLAY_FOLDER) || url.contains(OP_DISPLAY_BOOKMARK) || url.contains(OP_DISPLAY_NEWSBIN) || url.contains(OP_DISPLAY_NEWS) || url.contains(OP_DISPLAY_SEARCHMARK) || URIUtils.ABOUT_BLANK.equals(url);
  260. }
  261. /**
  262. * Check wether the given URL contains one of the resource-operations of this
  263. * Server.
  264. *
  265. * @param url The URL to Test for a Resource Operation.
  266. * @return Returns <code>TRUE</code> if the given URL is a resource-operation.
  267. */
  268. public boolean isResourceOperation(String url) {
  269. if (!StringUtils.isSet(url))
  270. return false;
  271. return url.contains(OP_RESOURCE);
  272. }
  273. /**
  274. * @param path the path to the resource in the plugin.
  275. * @return a url that can be used to access the resource.
  276. */
  277. public String toResourceUrl(String path) {
  278. StringBuilder url = new StringBuilder();
  279. url.append(PROTOCOL).append("://").append(LOCALHOST).append(':').append(fPort).append("/"); //$NON-NLS-1$ //$NON-NLS-2$
  280. url.append("?").append(OP_RESOURCE).append(path); //$NON-NLS-1$
  281. return url.toString();
  282. }
  283. /**
  284. * Creates a valid URL for the given Input
  285. *
  286. * @param id The ID of the Viewer
  287. * @param input The Input of the Viewer
  288. * @return a valid URL for the given Input
  289. */
  290. public String toUrl(String id, Object input) {
  291. /* Handle this Case */
  292. if (input == null)
  293. return URIUtils.ABOUT_BLANK;
  294. StringBuilder url = new StringBuilder();
  295. url.append(PROTOCOL).append("://").append(LOCALHOST).append(':').append(fPort).append("/"); //$NON-NLS-1$ //$NON-NLS-2$
  296. /* Append the ID */
  297. url.append("?").append(ID).append(id); //$NON-NLS-1$
  298. /* Wrap into Object Array */
  299. if (!(input instanceof Object[]))
  300. input = new Object[] { input };
  301. /* Input is an Array of Objects */
  302. List<Long> news = new ArrayList<Long>();
  303. List<Long> bookmarks = new ArrayList<Long>();
  304. List<Long> newsbins = new ArrayList<Long>();
  305. List<Long> searchmarks = new ArrayList<Long>();
  306. List<Long> folders = new ArrayList<Long>();
  307. /* Split into BookMarks, NewsBins, SearchMarks and News */
  308. for (Object obj : (Object[]) input) {
  309. if (obj instanceof FolderNewsMarkReference)
  310. folders.add(getId(obj));
  311. else if (obj instanceof IBookMark || obj instanceof BookMarkReference)
  312. bookmarks.add(getId(obj));
  313. else if (obj instanceof INewsBin || obj instanceof NewsBinReference)
  314. newsbins.add(getId(obj));
  315. else if (obj instanceof ISearchMark || obj instanceof SearchMarkReference)
  316. searchmarks.add(getId(obj));
  317. else if (obj instanceof INews || obj instanceof NewsReference)
  318. news.add(getId(obj));
  319. else if (obj instanceof EntityGroup) {
  320. List<EntityGroupItem> items = ((EntityGroup) obj).getItems();
  321. for (EntityGroupItem item : items) {
  322. IEntity entity = item.getEntity();
  323. if (entity instanceof INews)
  324. news.add(getId(entity));
  325. }
  326. }
  327. }
  328. /* Append Parameter for Folders */
  329. appendParameters(url, folders, OP_DISPLAY_FOLDER);
  330. /* Append Parameter for Bookmarks */
  331. appendParameters(url, bookmarks, OP_DISPLAY_BOOKMARK);
  332. /* Append Parameter for Newsbins */
  333. appendParameters(url, newsbins, OP_DISPLAY_NEWSBIN);
  334. /* Append Parameter for SearchMarks */
  335. appendParameters(url, searchmarks, OP_DISPLAY_SEARCHMARK);
  336. /* Append Parameter for News */
  337. appendParameters(url, news, OP_DISPLAY_NEWS);
  338. return url.toString();
  339. }
  340. private void appendParameters(StringBuilder url, List<Long> ids, String op) {
  341. if (!ids.isEmpty()) {
  342. url.append("&").append(op); //$NON-NLS-1$
  343. for (Long id : ids)
  344. url.append(id).append(',');
  345. /* Remove the last added ',' */
  346. url.deleteCharAt(url.length() - 1);
  347. }
  348. }
  349. private Long getId(Object obj) {
  350. if (obj instanceof IEntity)
  351. return ((IEntity) obj).getId();
  352. else if (obj instanceof ModelReference)
  353. return ((ModelReference) obj).getId();
  354. return null;
  355. }
  356. private void listen() {
  357. /* Create a Job to listen for Requests */
  358. fServerJob = new Job("Local News Viewer Server") { //$NON-NLS-1$
  359. @Override
  360. protected IStatus run(IProgressMonitor monitor) {
  361. /* Listen as long not canceled */
  362. while (!monitor.isCanceled()) {
  363. BufferedReader buffReader = null;
  364. Socket socket = null;
  365. try {
  366. /* Blocks until Socket accepted */
  367. socket = fSocket.accept();
  368. /* Read Incoming Message */
  369. buffReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
  370. String message = buffReader.readLine();
  371. /* Process Message */
  372. if (StringUtils.isSet(message))
  373. safeProcess(socket, message);
  374. } catch (IOException e) {
  375. /* Ignore */
  376. }
  377. /* Cleanup */
  378. finally {
  379. /* Close the Reader */
  380. try {
  381. if (buffReader != null)
  382. buffReader.close();
  383. } catch (Exception e) {
  384. /* Ignore */
  385. }
  386. /* Close the Socket */
  387. try {
  388. if (socket != null)
  389. socket.close();
  390. } catch (Exception e) {
  391. /* Ignore */
  392. }
  393. }
  394. }
  395. return Status.OK_STATUS;
  396. }
  397. };
  398. /* Set as System-Job and Schedule */
  399. fServerJob.setSystem(true);
  400. fServerJob.schedule();
  401. }
  402. /* Process Message in Safe-Runnable */
  403. private void safeProcess(final Socket socket, final String message) {
  404. SafeRunner.run(new LoggingSafeRunnable() {
  405. public void run() throws Exception {
  406. /* This is a Display-Operation */
  407. if (isDisplayOperation(message))
  408. processDisplayOperation(socket, message);
  409. /* This is a Resource-Operation */
  410. else if (isResourceOperation(message))
  411. processResourceOperation(socket, message);
  412. /* This is a startup handshake */
  413. else
  414. processHandshake(message);
  415. }
  416. });
  417. }
  418. /* Process Handshake-Message */
  419. private void processHandshake(String message) {
  420. if (fHandshakeHandler != null)
  421. fHandshakeHandler.handle(message);
  422. }
  423. private void processResourceOperation(Socket socket, String message) {
  424. /* Substring to get the Parameters String */
  425. int start = message.indexOf(OP_RESOURCE) + OP_RESOURCE.length();
  426. int end = message.indexOf(' ', start);
  427. String parameter = message.substring(start, end);
  428. /* Write HTML to the Receiver */
  429. BufferedOutputStream outS = null;
  430. try {
  431. outS = new BufferedOutputStream(socket.getOutputStream());
  432. CoreUtils.copy(OwlUI.class.getResourceAsStream(parameter), outS);
  433. } catch (IOException e) {
  434. /* Ignore */
  435. }
  436. /* Cleanup */
  437. finally {
  438. if (outS != null) {
  439. try {
  440. outS.close();
  441. } catch (IOException e) {
  442. /* Ignore */
  443. }
  444. }
  445. }
  446. }
  447. /* Process Message by looking for operations */
  448. private void processDisplayOperation(Socket socket, String message) {
  449. List<Object> elements = new ArrayList<Object>();
  450. /* Substring to get the Parameters String */
  451. int start = message.indexOf('/');
  452. int end = message.indexOf(' ', start);
  453. String parameters = message.substring(start, end);
  454. /* Retrieve the ID */
  455. String viewerId = null;
  456. int idIndex = parameters.indexOf(ID);
  457. if (idIndex >= 0) {
  458. start = idIndex + ID.length();
  459. end = parameters.indexOf('&', start);
  460. if (end < 0)
  461. end = parameters.length();
  462. viewerId = parameters.substring(start, end);
  463. }
  464. /* Ask for ContentProvider of Viewer */
  465. ContentViewer viewer = fRegistry.get(viewerId);
  466. if (viewer instanceof NewsBrowserViewer && viewer.getContentProvider() != null) {
  467. IStructuredContentProvider newsContentProvider = (IStructuredContentProvider) viewer.getContentProvider();
  468. /* Look for Folders that are to displayed */
  469. int displayFolderIndex = parameters.indexOf(OP_DISPLAY_FOLDER);
  470. if (displayFolderIndex >= 0) {
  471. start = displayFolderIndex + OP_DISPLAY_FOLDER.length();
  472. end = parameters.indexOf('&', start);
  473. if (end < 0)
  474. end = parameters.length();
  475. StringTokenizer tokenizer = new StringTokenizer(parameters.substring(start, end), ",");//$NON-NLS-1$
  476. while (tokenizer.hasMoreElements()) {
  477. FolderReference ref = new FolderReference(Long.valueOf((String) tokenizer.nextElement()));
  478. elements.addAll(Arrays.asList(newsContentProvider.getElements(ref)));
  479. }
  480. }
  481. /* Look for BookMarks that are to displayed */
  482. int displayBookMarkIndex = parameters.indexOf(OP_DISPLAY_BOOKMARK);
  483. if (displayBookMarkIndex >= 0) {
  484. start = displayBookMarkIndex + OP_DISPLAY_BOOKMARK.length();
  485. end = parameters.indexOf('&', start);
  486. if (end < 0)
  487. end = parameters.length();
  488. StringTokenizer tokenizer = new StringTokenizer(parameters.substring(start, end), ",");//$NON-NLS-1$
  489. while (tokenizer.hasMoreElements()) {
  490. BookMarkReference ref = new BookMarkReference(Long.valueOf((String) tokenizer.nextElement()));
  491. elements.addAll(Arrays.asList(newsContentProvider.getElements(ref)));
  492. }
  493. }
  494. /* Look for NewsBins that are to displayed */
  495. int displayNewsBinsIndex = parameters.indexOf(OP_DISPLAY_NEWSBIN);
  496. if (displayNewsBinsIndex >= 0) {
  497. start = displayNewsBinsIndex + OP_DISPLAY_NEWSBIN.length();
  498. end = parameters.indexOf('&', start);
  499. if (end < 0)
  500. end = parameters.length();
  501. StringTokenizer tokenizer = new StringTokenizer(parameters.substring(start, end), ",");//$NON-NLS-1$
  502. while (tokenizer.hasMoreElements()) {
  503. NewsBinReference ref = new NewsBinReference(Long.valueOf((String) tokenizer.nextElement()));
  504. elements.addAll(Arrays.asList(newsContentProvider.getElements(ref)));
  505. }
  506. }
  507. /* Look for SearchMarks that are to displayed */
  508. int displaySearchMarkIndex = parameters.indexOf(OP_DISPLAY_SEARCHMARK);
  509. if (displaySearchMarkIndex >= 0) {
  510. start = displaySearchMarkIndex + OP_DISPLAY_SEARCHMARK.length();
  511. end = parameters.indexOf('&', start);
  512. if (end < 0)
  513. end = parameters.length();
  514. StringTokenizer tokenizer = new StringTokenizer(parameters.substring(start, end), ",");//$NON-NLS-1$
  515. while (tokenizer.hasMoreElements()) {
  516. SearchMarkReference ref = new SearchMarkReference(Long.valueOf((String) tokenizer.nextElement()));
  517. elements.addAll(Arrays.asList(newsContentProvider.getElements(ref)));
  518. }
  519. }
  520. /* Look for News that are to displayed */
  521. int displayNewsIndex = parameters.indexOf(OP_DISPLAY_NEWS);
  522. if (displayNewsIndex >= 0) {
  523. start = displayNewsIndex + OP_DISPLAY_NEWS.length();
  524. end = parameters.indexOf('&', start);
  525. if (end < 0)
  526. end = parameters.length();
  527. StringTokenizer tokenizer = new StringTokenizer(parameters.substring(start, end), ",");//$NON-NLS-1$
  528. while (tokenizer.hasMoreElements()) {
  529. NewsReference ref = new NewsReference(Long.valueOf((String) tokenizer.nextElement()));
  530. elements.addAll(Arrays.asList(newsContentProvider.getElements(ref)));
  531. }
  532. }
  533. }
  534. /* Reply to the Socket */
  535. reply(socket, viewerId, elements.toArray());
  536. }
  537. /* Create HTML out of the Elements and reply to the Socket */
  538. private void reply(Socket socket, String viewerId, Object[] elements) {
  539. /* Only responsible for Viewer-Concerns */
  540. if (viewerId == null)
  541. return;
  542. /* Retrieve Viewer */
  543. ContentViewer viewer = fRegistry.get(viewerId);
  544. /* Might be bad timing */
  545. if (viewer == null)
  546. return;
  547. /* Ask for sorted Elements */
  548. NewsBrowserLabelProvider labelProvider = (NewsBrowserLabelProvider) viewer.getLabelProvider();
  549. Object[] children = new Object[0];
  550. if (viewer instanceof NewsBrowserViewer)
  551. children = ((NewsBrowserViewer) viewer).getFlattendChildren(elements);
  552. /* Write HTML to the Receiver */
  553. BufferedWriter writer = null;
  554. try {
  555. boolean portable = Controller.getDefault().isPortable();
  556. writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
  557. if (Application.IS_WINDOWS && portable)
  558. writer.append("HTTP/1.1 205 OK").append(CRLF); //$NON-NLS-1$
  559. else
  560. writer.append("HTTP/1.1 200 OK").append(CRLF); //$NON-NLS-1$
  561. synchronized (RFC_1123_DATE) {
  562. writer.append("Date: ").append(RFC_1123_DATE.format(new Date())).append(CRLF); //$NON-NLS-1$
  563. }
  564. writer.append("Server: RSSOwl Local Server").append(CRLF); //$NON-NLS-1$
  565. writer.append("Content-Type: text/html; charset=UTF-8").append(CRLF); //$NON-NLS-1$
  566. writer.append("Connection: close").append(CRLF); //$NON-NLS-1$
  567. writer.append("Expires: 0").append(CRLF); //$NON-NLS-1$
  568. writer.write(CRLF);
  569. /* Begin HTML */
  570. writer.write("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n"); //$NON-NLS-1$
  571. /* Windows only: Mark of the Web */
  572. if (Application.IS_WINDOWS) {
  573. writer.write(IE_MOTW);
  574. writer.write("\n"); //$NON-NLS-1$
  575. }
  576. writer.write("<html>\n <head>\n"); //$NON-NLS-1$
  577. /* Append Base URI if available */
  578. String base = getBase(children);
  579. if (base != null) {
  580. writer.write(" <base href=\""); //$NON-NLS-1$
  581. writer.write(base);
  582. writer.write("\">"); //$NON-NLS-1$
  583. }
  584. writer.write("\n <title></title>"); //$NON-NLS-1$
  585. writer.write("\n <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n"); //$NON-NLS-1$
  586. /* CSS */
  587. labelProvider.writeCSS(writer);
  588. /* Open Body */
  589. writer.write(" </head>\n <body id=\"owlbody\">\n"); //$NON-NLS-1$
  590. /* Output each Element as HTML */
  591. for (int i = 0; i < children.length; i++) {
  592. String html = unicodeToEntities(labelProvider.getText(children[i], i));
  593. writer.write(html);
  594. }
  595. /* End HTML */
  596. writer.write("\n </body>\n</html>"); //$NON-NLS-1$
  597. } catch (IOException e) {
  598. /* Ignore */
  599. }
  600. /* Cleanup */
  601. finally {
  602. if (writer != null) {
  603. try {
  604. writer.close();
  605. } catch (IOException e) {
  606. /* Ignore */
  607. }
  608. }
  609. }
  610. }
  611. /* Find BASE-Information from Elements */
  612. private String getBase(Object elements[]) {
  613. for (Object object : elements) {
  614. if (object instanceof INews) {
  615. INews news = (INews) object;
  616. /* Base-Information explicitly set */
  617. if (news.getBase() != null)
  618. return news.getBase().toString();
  619. /* Use Feed's Link as fallback */
  620. return news.getFeedLinkAsText();
  621. }
  622. }
  623. return null;
  624. }
  625. private String unicodeToEntities(String str) {
  626. StringBuilder strBuf = new StringBuilder();
  627. /* For each character */
  628. for (int i = 0; i < str.length(); i++) {
  629. char ch = str.charAt(i);
  630. /* This is a non ASCII, non Whitespace character */
  631. if (!((ch >= 0x0020) && (ch <= 0x007e)) && !Character.isWhitespace(ch)) {
  632. strBuf.append("&#x"); //$NON-NLS-1$
  633. String hex = Integer.toHexString(ch & 0xFFFF);
  634. if (hex.length() == 2)
  635. strBuf.append("00"); //$NON-NLS-1$
  636. strBuf.append(hex).append(";"); //$NON-NLS-1$
  637. }
  638. /* This is an ASCII character */
  639. else {
  640. strBuf.append(ch);
  641. }
  642. }
  643. return strBuf.toString();
  644. }
  645. /**
  646. * @return the port used by this server.
  647. */
  648. public int getPort() {
  649. return SOCKET_PORT;
  650. }
  651. /**
  652. * @return the host used by this server.
  653. */
  654. public String getHost() {
  655. return LOCALHOST;
  656. }
  657. }