/*
* Copyright (c) 2004-2011 Marco Maccaferri and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Marco Maccaferri - initial API and implementation
*/
package org.eclipsetrader.news.internal;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.ValidationEvent;
import javax.xml.bind.ValidationEventHandler;
import javax.xml.namespace.QName;
import javax.xml.transform.stream.StreamSource;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.IJobManager;
import org.eclipse.core.runtime.jobs.ILock;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.MultiRule;
import org.eclipsetrader.core.instruments.ISecurity;
import org.eclipsetrader.news.core.HeadLineStatus;
import org.eclipsetrader.news.core.IHeadLine;
import org.eclipsetrader.news.core.INewsProvider;
import org.eclipsetrader.news.core.INewsService;
import org.eclipsetrader.news.core.INewsServiceListener;
import org.eclipsetrader.news.core.INewsServiceRunnable;
import org.eclipsetrader.news.core.NewsEvent;
import org.eclipsetrader.news.internal.repository.HeadLine;
public class NewsService implements INewsService, ISchedulingRule {
public static final String HEADLINES_FILE = "headlines.xml"; //$NON-NLS-1$
private List<IHeadLine> headLines = new ArrayList<IHeadLine>();
private Map<ISecurity, List<IHeadLine>> securityMap = new HashMap<ISecurity, List<IHeadLine>>();
private ListenerList listeners = new ListenerList(ListenerList.IDENTITY);
private List<HeadLineStatus> status = new ArrayList<HeadLineStatus>();
private boolean holdNotifications;
private List<INewsProvider> providers = new ArrayList<INewsProvider>();
private IJobManager jobManager;
private final ILock lock;
public NewsService() {
jobManager = Job.getJobManager();
lock = jobManager.newLock();
}
public void startUp(IProgressMonitor monitor) throws JAXBException {
File file = Activator.getDefault().getStateLocation().append(HEADLINES_FILE).toFile();
if (file.exists()) {
JAXBContext jaxbContext = JAXBContext.newInstance(HeadLine[].class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
unmarshaller.setEventHandler(new ValidationEventHandler() {
@Override
public boolean handleEvent(ValidationEvent event) {
Status status = new Status(IStatus.WARNING, Activator.PLUGIN_ID, 0, "Error validating XML: " + event.getMessage(), null); //$NON-NLS-1$
Activator.getDefault().getLog().log(status);
return true;
}
});
JAXBElement<HeadLine[]> element = unmarshaller.unmarshal(new StreamSource(file), HeadLine[].class);
headLines.addAll(Arrays.asList(element.getValue()));
}
IConfigurationElement[] elements = getProvidersConfigurationElements();
for (int i = 0; i < elements.length; i++) {
try {
INewsProvider newsProvider = (INewsProvider) elements[i].createExecutableExtension("class");
headLines.addAll(Arrays.asList(newsProvider.getHeadLines()));
providers.add(newsProvider);
} catch (Exception e) {
// TODO Log
}
}
Date limitDate = getLimitDate();
for (Iterator<IHeadLine> iter = headLines.iterator(); iter.hasNext();) {
if (iter.next().getDate().before(limitDate)) {
iter.remove();
}
}
updateSecurityMap();
}
public void shutDown(IProgressMonitor monitor) throws JAXBException, IOException {
for (INewsProvider newsProvider : providers) {
newsProvider.stop();
}
File file = Activator.getDefault().getStateLocation().append(HEADLINES_FILE).toFile();
if (file.exists()) {
file.delete();
}
JAXBContext jaxbContext = JAXBContext.newInstance(HeadLine[].class);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setEventHandler(new ValidationEventHandler() {
@Override
public boolean handleEvent(ValidationEvent event) {
Status status = new Status(IStatus.WARNING, Activator.PLUGIN_ID, 0, "Error validating XML: " + event.getMessage(), null); //$NON-NLS-1$
Activator.getDefault().getLog().log(status);
return true;
}
});
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.setProperty(Marshaller.JAXB_ENCODING, System.getProperty("file.encoding")); //$NON-NLS-1$
List<HeadLine> list = new ArrayList<HeadLine>();
for (IHeadLine h : headLines) {
if (h instanceof HeadLine) {
list.add((HeadLine) h);
}
}
JAXBElement<HeadLine[]> element = new JAXBElement<HeadLine[]>(new QName("list"), HeadLine[].class, list.toArray(new HeadLine[list.size()]));
marshaller.marshal(element, new FileWriter(file));
}
/* (non-Javadoc)
* @see org.eclipsetrader.news.core.INewsService#getHeadLines()
*/
@Override
public IHeadLine[] getHeadLines() {
return headLines.toArray(new IHeadLine[headLines.size()]);
}
/* (non-Javadoc)
* @see org.eclipsetrader.news.core.INewsService#getHeadLinesFor(org.eclipsetrader.core.instruments.ISecurity)
*/
@Override
public IHeadLine[] getHeadLinesFor(ISecurity security) {
List<IHeadLine> l = securityMap.get(security);
return l != null ? l.toArray(new IHeadLine[l.size()]) : new IHeadLine[0];
}
/* (non-Javadoc)
* @see org.eclipsetrader.news.core.INewsService#hasHeadLinesFor(org.eclipsetrader.core.instruments.ISecurity)
*/
@Override
public boolean hasHeadLinesFor(ISecurity security) {
List<IHeadLine> l = securityMap.get(security);
return l != null && l.size() != 0;
}
/* (non-Javadoc)
* @see org.eclipsetrader.news.core.INewsService#hasUnreadedHeadLinesFor(org.eclipsetrader.core.instruments.ISecurity)
*/
@Override
public boolean hasUnreadedHeadLinesFor(ISecurity security) {
List<IHeadLine> l = securityMap.get(security);
if (l != null) {
for (IHeadLine h : l) {
if (!h.isReaded()) {
return true;
}
}
}
return false;
}
/* (non-Javadoc)
* @see org.eclipsetrader.news.core.INewsService#addNewsServiceListener(org.eclipsetrader.news.core.INewsServiceListener)
*/
@Override
public void addNewsServiceListener(INewsServiceListener listener) {
listeners.add(listener);
}
/* (non-Javadoc)
* @see org.eclipsetrader.news.core.INewsService#removeNewsServiceListener(org.eclipsetrader.news.core.INewsServiceListener)
*/
@Override
public void removeNewsServiceListener(INewsServiceListener listener) {
listeners.remove(listener);
}
/* (non-Javadoc)
* @see org.eclipsetrader.news.core.INewsService#addHeadLines(org.eclipsetrader.news.core.IHeadLine[])
*/
@Override
public void addHeadLines(IHeadLine[] newHeadLines) {
synchronized (status) {
Date limitDate = getLimitDate();
for (int i = 0; i < newHeadLines.length; i++) {
if (newHeadLines[i].getDate().before(limitDate)) {
continue;
}
if (!headLines.contains(newHeadLines[i])) {
headLines.add(newHeadLines[i]);
status.add(new HeadLineStatus(HeadLineStatus.ADDED, newHeadLines[i]));
}
}
if (!holdNotifications) {
fireHeadLineStatusEvent();
}
}
}
/* (non-Javadoc)
* @see org.eclipsetrader.news.core.INewsService#removeHeadLines(org.eclipsetrader.news.core.IHeadLine[])
*/
@Override
public void removeHeadLines(IHeadLine[] oldHeadLines) {
synchronized (status) {
for (int i = 0; i < oldHeadLines.length; i++) {
if (headLines.contains(oldHeadLines[i])) {
headLines.remove(oldHeadLines[i]);
status.add(new HeadLineStatus(HeadLineStatus.REMOVED, oldHeadLines[i]));
}
}
if (!holdNotifications) {
fireHeadLineStatusEvent();
}
}
}
/* (non-Javadoc)
* @see org.eclipsetrader.news.core.INewsService#updateHeadLines(org.eclipsetrader.news.core.IHeadLine[])
*/
@Override
public void updateHeadLines(IHeadLine[] updatedHeadLines) {
synchronized (status) {
for (int i = 0; i < updatedHeadLines.length; i++) {
if (headLines.contains(updatedHeadLines[i])) {
status.add(new HeadLineStatus(HeadLineStatus.UPDATED, updatedHeadLines[i]));
}
}
if (!holdNotifications) {
fireHeadLineStatusEvent();
}
}
}
protected void updateSecurityMap() {
securityMap.clear();
for (IHeadLine headLine : headLines) {
for (ISecurity security : headLine.getMembers()) {
List<IHeadLine> list = securityMap.get(security);
if (list == null) {
list = new ArrayList<IHeadLine>();
securityMap.put(security, list);
}
if (!list.contains(headLine)) {
list.add(headLine);
}
}
}
}
protected void fireHeadLineStatusEvent() {
NewsEvent event = new NewsEvent(this, status.toArray(new HeadLineStatus[status.size()]));
status.clear();
Object[] l = listeners.getListeners();
for (int i = 0; i < l.length; i++) {
try {
((INewsServiceListener) l[i]).newsServiceUpdate(event);
} catch (Exception e) {
Status status = new Status(IStatus.ERROR, Activator.PLUGIN_ID, 0, "Error notifying event to listener", e); //$NON-NLS-1$
Activator.getDefault().getLog().log(status);
} catch (LinkageError e) {
Status status = new Status(IStatus.ERROR, Activator.PLUGIN_ID, 0, "Error notifying event to listener", e); //$NON-NLS-1$
Activator.getDefault().getLog().log(status);
}
}
}
/* (non-Javadoc)
* @see org.eclipsetrader.news.core.INewsService#runInService(org.eclipsetrader.news.core.INewsServiceRunnable, org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
public IStatus runInService(INewsServiceRunnable runnable, IProgressMonitor monitor) {
return runInService(runnable, this, monitor);
}
/* (non-Javadoc)
* @see org.eclipsetrader.news.core.INewsService#runInService(org.eclipsetrader.news.core.INewsServiceRunnable, org.eclipse.core.runtime.jobs.ISchedulingRule, org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
public IStatus runInService(INewsServiceRunnable runnable, ISchedulingRule rule, IProgressMonitor monitor) {
IStatus status;
jobManager.beginRule(rule, monitor);
try {
lock.acquire();
holdNotifications = true;
try {
status = runnable.run(monitor);
} catch (Exception e) {
status = new Status(IStatus.ERROR, Activator.PLUGIN_ID, 0, "Error running service task", e); //$NON-NLS-1$
Activator.getDefault().getLog().log(status);
} catch (LinkageError e) {
status = new Status(IStatus.ERROR, Activator.PLUGIN_ID, 0, "Error running service task", e); //$NON-NLS-1$
Activator.getDefault().getLog().log(status);
}
updateSecurityMap();
fireHeadLineStatusEvent();
} catch (Exception e) {
status = new Status(IStatus.ERROR, Activator.PLUGIN_ID, 0, "Error running service task", e); //$NON-NLS-1$
Activator.getDefault().getLog().log(status);
} finally {
holdNotifications = false;
lock.release();
jobManager.endRule(rule);
}
return status;
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.jobs.ISchedulingRule#contains(org.eclipse.core.runtime.jobs.ISchedulingRule)
*/
@Override
public boolean contains(ISchedulingRule rule) {
if (this == rule) {
return true;
}
if (rule instanceof MultiRule) {
MultiRule multi = (MultiRule) rule;
ISchedulingRule[] children = multi.getChildren();
for (int i = 0; i < children.length; i++) {
if (!contains(children[i])) {
return false;
}
}
return true;
}
return false;
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.jobs.ISchedulingRule#isConflicting(org.eclipse.core.runtime.jobs.ISchedulingRule)
*/
@Override
public boolean isConflicting(ISchedulingRule rule) {
if (this == rule) {
return true;
}
return false;
}
protected IConfigurationElement[] getProvidersConfigurationElements() {
List<IConfigurationElement> elements = new ArrayList<IConfigurationElement>();
IExtensionRegistry registry = Platform.getExtensionRegistry();
IExtensionPoint extensionPoint = registry.getExtensionPoint(Activator.PROVIDER_EXTENSION_POINT);
if (extensionPoint != null) {
for (IConfigurationElement e : extensionPoint.getConfigurationElements()) {
if (e.getName().equals("provider")) {
elements.add(e);
}
}
}
return elements.toArray(new IConfigurationElement[elements.size()]);
}
/* (non-Javadoc)
* @see org.eclipsetrader.news.core.INewsService#getProviders()
*/
@Override
public INewsProvider[] getProviders() {
return providers.toArray(new INewsProvider[providers.size()]);
}
protected Date getLimitDate() {
Calendar limit = Calendar.getInstance();
limit.set(Calendar.HOUR_OF_DAY, 0);
limit.set(Calendar.MINUTE, 0);
limit.set(Calendar.SECOND, 0);
limit.set(Calendar.MILLISECOND, 0);
limit.add(Calendar.DATE, -Activator.getDefault().getPreferenceStore().getInt(Activator.PREFS_DATE_RANGE));
return limit.getTime();
}
}