/*
* WebComponent.java
*
* Created on March 26, 2007, 6:04 PM
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/
package org.atomojo.www;
import java.net.URI;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Level;
import org.atomojo.app.client.Entry;
import org.atomojo.app.client.FeedClient;
import org.atomojo.app.client.FeedDestination;
import org.atomojo.app.client.Link;
import org.atomojo.app.client.Ontology;
import org.atomojo.app.client.Text;
import org.infoset.xml.Document;
import org.infoset.xml.Element;
import org.infoset.xml.Name;
import org.infoset.xml.XMLException;
import org.restlet.Client;
import org.restlet.Component;
import org.restlet.Context;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.Restlet;
import org.restlet.Server;
import org.restlet.data.Protocol;
import org.restlet.data.Reference;
import org.restlet.data.Status;
/**
*
* @author alex
*/
public class WebComponent extends Component {
static final URI HOST_TERM = Ontology.getDefaultInstance().find(ConfiguredHost.T_BASE,"host").getURI();
static final Name VIEWPORT = Name.create("{http://www.w3.org/2007/03/xproc}viewport");
static final Name XSLT = Name.create("{http://www.w3.org/2007/03/xproc}xslt");
static final Name INPUT = Name.create("{http://www.w3.org/2007/03/xproc}input");
static final Name INLINE = Name.create("{http://www.w3.org/2007/03/xproc}inline");
public static final String LINKS_ATTR = "org.atomojo.www.app.links";
class AutoConfiguration {
Configuration.Interface iface;
Link link;
Map<String, ConfiguredHost> hosts;
Map<String, ConfiguredHost> confHosts;
boolean changes;
Client webClient;
AutoConfiguration(Configuration.Interface iface, Link link) {
this.iface = iface;
this.link = link;
this.hosts = new TreeMap<String, ConfiguredHost>();
this.confHosts = ifaceHosts.get(iface.getKey());
this.webClient = getContext().getClientDispatcher();
}
public void configure() {
FeedClient client = new FeedClient(webClient,new Reference(link.getLink()));
client.setIdentity(link.getUsername(), link.getPassword());
getContext().getLogger().info("Loading configuration for " + iface.getAddress() + ":" + iface.getPort() + " from " + link.getLink());
changes = false;
final Map<String, Boolean> found = new TreeMap<String, Boolean>();
try {
client.get(new FeedDestination() {
public void onFeed(Document feedDoc) {
}
public void onEntry(Document entryDoc) {
Entry entry = new Entry(entryDoc);
entry.index();
if (entry.getTerm(HOST_TERM) == null) {
return;
}
Text text = entry.getContent();
if (text == null || !text.isXML()) {
getContext().getLogger().warning("Ignoring host entry with missing or has incorrectly typed content for configuration.");
return;
}
Element hostE = text.getContent();
if (hostE == null) {
getContext().getLogger().warning("Ignoring host entry with empty content.");
return;
}
Date edited = null;
try {
edited = entry.getEdited();
if (edited == null) {
edited = entry.getUpdated();
}
} catch (ParseException ex) {
getContext().getLogger().warning("Ignoring host entry with malformed date in updated or edited element: " + ex.getMessage());
return;
}
if (edited == null) {
getContext().getLogger().warning("Ignoring host entry without updated or edited element.");
return;
}
found.put(entry.getId(), Boolean.TRUE);
ConfiguredHost autoHost = hosts.get(entry.getId());
if (autoHost==null) {
getLogger().info("New host detected in entry "+entry.getId());
} else if (autoHost.edited.getTime() < edited.getTime()) {
// the entry has changed, so delete
getContext().getLogger().info("Entry changed for host " + autoHost.getVirtualHost().getHostDomain() + ", reconfiguring.");
getHosts().remove(autoHost.getVirtualHost());
autoHost = null;
}
if (autoHost != null) {
autoHost.check();
return;
}
// Create configuration
changes = true;
try {
Configuration.Host host = config.createHost(hostE);
String hostName = host.getName();
if (hostName == null) {
getLogger().severe("Host entry " + entry.getId() + " does not have a host name attribute.");
return;
}
getLogger().info("Auto-configuring host " + host.getName());
for (Map<String, ConfiguredHost> confHosts : ifaceHosts.values()) {
if (confHosts.get(host.getName()) != null) {
getLogger().warning("Ignoring duplicate host for " + host.getName() + " from auto-configuration.");
return;
}
}
Context hostContext = getContext().createChildContext();
ConfiguredHost confHost = new ConfiguredHost(hostContext, getInternalRouter(),iface, host, edited, false);
// The auto host should not be in confHosts as it isn't static
//confHosts.put(host.getName(), confHost);
hosts.put(entry.getId(), confHost);
getHosts().add(confHost.getVirtualHost());
} catch (XMLException ex) {
getLogger().log(Level.SEVERE, "Cannot load host configuration.", ex);
}
}
});
// remove all entries that have been deleted
List<String> toRemove = new ArrayList<String>();
for (String id : hosts.keySet()) {
if (found.get(id) == null) {
ConfiguredHost autoHost = hosts.get(id);
getLogger().info("Removing host " + autoHost.getVirtualHost().getHostDomain());
changes = true;
toRemove.add(id);
getHosts().remove(autoHost.getVirtualHost());
confHosts.remove(autoHost.getVirtualHost().getHostDomain());
}
}
for (String id : toRemove) {
hosts.remove(id);
}
if (changes && WebComponent.this.isStarted()) {
getLogger().info("Updating hosts...");
WebComponent.this.updateHosts();
/*
restarting = true;
stop();
start();
restarting = false;
*/
}
} catch (Exception ex) {
getContext().getLogger().log(Level.SEVERE, "Fatal error while getting auto configuration feed at " + link.getLink(), ex);
}
getContext().getLogger().info("Configuration " + link.getLink() + " loaded.");
}
}
class AutoConfProcess implements Runnable {
boolean run = true;
public void run() {
while (run) {
try {
for (AutoConfiguration autoconf : autoconfs) {
autoconf.configure();
if (!run) {
break;
}
}
getLogger().info("Waiting " + (config.getAutoConfigurationCheckWait() / 1000) + "s to check configuration.");
Thread.currentThread().sleep(config.getAutoConfigurationCheckWait());
for (Map<String, ConfiguredHost> confHosts : ifaceHosts.values()) {
for (ConfiguredHost confHost : confHosts.values()) {
if (confHost.isStatic()) {
confHost.check();
}
}
}
} catch (InterruptedException ex) {
}
}
getLogger().info("Auto configuration thread exiting.");
}
}
static public String LOG_NAME = "org.atomojo.hosts";
Configuration config;
Map<String, Map<String, ConfiguredHost>> ifaceHosts;
List<AutoConfiguration> autoconfs;
AutoConfProcess autoconfProc;
Thread autoconfThread;
boolean restarting = false;
boolean hasStaticAutoConf = false;
/** Creates a new instance of WebComponent */
public WebComponent(Configuration config) {
this.config = config;
this.ifaceHosts = new TreeMap<String, Map<String, ConfiguredHost>>();
this.autoconfs = new ArrayList<AutoConfiguration>();
getLogService().setLoggerName(LOG_NAME);
if (config.getKeyStorePath() != null) {
getContext().getParameters().add("keystorePath", config.getKeyStorePath().getAbsolutePath());
getContext().getParameters().add("keystorePassword", config.getKeyStorePassword());
getContext().getParameters().add("keyPassword", config.getKeyStorePassword());
}
/*
ScriptEngineManager manager = new ScriptEngineManager();
for (ScriptEngineFactory factory : manager.getEngineFactories()) {
StringBuilder w = new StringBuilder();
w.append("\nLanguage Name: " + factory.getLanguageName());
w.append("\nLanguage Version: " + factory.getLanguageVersion());
w.append("\nEngine Name: " + factory.getEngineName());
w.append("\nEngine Version: " + factory.getEngineVersion());
w.append("\nExtensions:");
for (String ext : factory.getExtensions()) {
w.append(" " + ext);
}
w.append("\nMedia Types:");
for (String type : factory.getMimeTypes()) {
w.append(" " + type);
}
w.append("\n\n");
getLogger().info("Script Engines: " + w.toString());
}*/
for (Configuration.Interface iface : config.getInterfaces()) {
if (iface.isSecure()) {
getContext().getLogger().info("https listening on " + iface.getAddress() + ":" + iface.getPort());
Server server = getServers().add(Protocol.HTTPS, iface.getAddress().equals("*") ? null : iface.getAddress(), iface.getPort());
if (config.getKeyStorePath() != null) {
server.getContext().getParameters().add("keystorePath", config.getKeyStorePath().getAbsolutePath());
server.getContext().getParameters().add("keystorePassword", config.getKeyStorePassword());
server.getContext().getParameters().add("keyPassword", config.getKeyStorePassword());
}
} else {
getContext().getLogger().info("http listening on " + iface.getAddress() + ":" + iface.getPort());
getServers().add(Protocol.HTTP, iface.getAddress().equals("*") ? null : iface.getAddress(), iface.getPort());
}
Map<String, ConfiguredHost> confHosts = ifaceHosts.get(iface.getKey());
if (confHosts == null) {
confHosts = new TreeMap<String, ConfiguredHost>();
ifaceHosts.put(iface.getKey(), confHosts);
}
// Configure static hosts
for (String name : iface.getHosts().keySet()) {
final Configuration.Host host = iface.getHosts().get(name);
if (confHosts.get(host.getName()) != null) {
getLogger().warning("Ignoring duplicate host name " + host.getName());
} else {
Context hostContext = getContext().createChildContext();
ConfiguredHost confHost = new ConfiguredHost(hostContext, getInternalRouter(), iface, host, new Date(), true);
confHosts.put(host.getName(), confHost);
if (host.getLinks().get("autoconf") != null) {
hasStaticAutoConf = true;
}
getHosts().add(confHost.getVirtualHost());
}
}
// Configure autoconf
List<Link> autoFeeds = iface.getLinks().get("autoconf");
if (autoFeeds != null) {
for (Link link : autoFeeds) {
addAutoConfiguration(iface, link);
}
}
}
this.getDefaultHost().attach(new Restlet() {
public void handle(Request request, Response response) {
response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
}
});
// Add the clients
getClients().add(Protocol.FILE);
getClients().add(Protocol.HTTP);
getClients().add(Protocol.HTTPS);
for (Server server : getServers()) {
try {
server.start();
} catch (Exception ex) {
getLogger().log(Level.SEVERE,"Cannot start server.",ex);
}
}
}
public void addAutoConfiguration(Configuration.Interface iface, Link link) {
AutoConfiguration autoconf = new AutoConfiguration(iface, link);
autoconfs.add(autoconf);
}
public void start()
throws Exception
{
super.start();
if (!restarting) {
if (autoconfs.size() > 0 || hasStaticAutoConf) {
getLogger().info("Starting auto-configuration process...");
autoconfProc = new AutoConfProcess();
autoconfThread = new Thread(autoconfProc);
autoconfThread.start();
getLogger().info("...started.");
} else {
autoconfProc = null;
}
}
}
public void stop()
throws Exception
{
if (!restarting) {
if (autoconfProc != null) {
getLogger().info("Stopping auto-configuration process...");
autoconfProc.run = false;
synchronized (autoconfThread) {
autoconfThread.interrupt();
}
autoconfThread.join(2000);
getLogger().info("...stopped.");
}
}
super.stop();
}
}