/* ====================================================================
* The Apache Software License, Version 1.1
*
* Copyright (c) 2000-2001 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Apache" and "Apache Software Foundation" and
* "Apache Jetspeed" must not be used to endorse or promote products
* derived from this software without prior written permission. For
* written permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache" or
* "Apache Jetspeed", nor may "Apache" appear in their name, without
* prior written permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*/
package org.apache.jetspeed.services.urlmanager;
import org.apache.turbine.util.Log;
import org.apache.turbine.services.TurbineBaseService;
import org.apache.jetspeed.services.resources.JetspeedResources;
//import org.apache.turbine.util.Configurations;
//import org.apache.turbine.util.ExtendedProperties;
import org.apache.velocity.runtime.configuration.Configuration;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import java.util.*;
import java.io.*;
/**
* <p>This implementation of the URLManagerService is backed by a simple
* map persisted on disk in a properties file</p>
* Added: Support for proxies. <br>
* Example: (Set in <code>JetspeedResources.properties</code>)<br>
* <code>services.URLManager.proxy.http.host=myproxy.mydomain</code><br>
* <code>services.URLManager.proxy.http.port=81</code><br>
*
* @see URLManagerService
* @author <a href="mailto:raphael@apache.org">Rapha�l Luta</a>
* @author <a href="mailto:sgala@hisitech.com">Santiago Gala</a>
* @version $Id: JetspeedURLManagerService.java,v 1.14 2003/02/19 12:40:57 sgala Exp $
*/
public class JetspeedURLManagerService
extends TurbineBaseService
implements URLManagerService {
/**
Map used to store all URL Information.
*/
private Map urls = new HashMap();
/**
Path to the properties file used for persisting the data
*/
private String path = null;
/**
Hashtable to store proxy configuration in
*/
private Hashtable proxies = new Hashtable();
/**
* Late init. Don't return control until early init says we're done.
*/
public void init( )
{
while( !getInit() ) {
try {
Thread.sleep(500);
} catch (InterruptedException ie ) {
Log.info("URLManager service: Waiting for init()..." );
}
}
}
/**
* Called during Turbine.init()
*
* @param config A ServletConfig.
*/
public synchronized void init( ServletConfig config )
{
//We have already been initialized...
if( getInit() ) return;
try
{
Log.info ( "JetspeedURLManagerService early init()....starting!");
// Proxy Settings are stored as 'services.URLManager.proxy.<protocol>.port' and as
// 'services.URLManager.proxy.<protocol>.port' in JetspeedResource.properties.
// Get a list of settings and store them in the hashtable
String prefix = "services." + URLManagerService.SERVICE_NAME + ".proxy.";
Iterator resourceKeys = JetspeedResources.getKeys( prefix );
String key, hashKey;
Object hashValue = null;
while( resourceKeys.hasNext() )
{
key = (String)resourceKeys.next();
hashKey = key.substring(prefix.length()).toLowerCase();
if ( hashKey.endsWith(".host") )
{
hashValue = JetspeedResources.getString(key);
proxies.put( hashKey, hashValue );
}
else if ( hashKey.endsWith(".port") )
{
hashValue = new Integer(JetspeedResources.getInt(key));
proxies.put( hashKey, hashValue );
}
}
path = JetspeedResources.getString( "services."+URLManagerService.SERVICE_NAME+".url" );
if ( path == null)
{
String tempdir = new String("WEB-INF/conf/datasources.properties");
String ps = System.getProperty("file.separator");
try
{
ServletContext sc = config.getServletContext();
tempdir = sc.getAttribute("javax.servlet.context.tempdir").toString()
+ ps + "jetspeed"
+ ps + "conf"
+ ps + "datasources.properties";
Log.debug("URLMangler: will create file in servlet temp directory " + tempdir);
}
catch (Exception e)
{
Log.debug("URLMangler: problems creating file in servlet temp directory "
+ " falling back to WEB-INF/conf : " + e);
}
path = tempdir;
}
else
{
Log.debug("URLMangler: will create file in user configured " + path);
path = config.getServletContext().getRealPath(path);
// should test for writability here and fallback to servlet tmp directory on failure
}
load();
Log.info ( "JetspeedURLManagerService early init()....finished!");
}
catch (Throwable t)
{
Log.error ( "Cannot initialize JetspeedURLManagerService!" );
Log.error (t);
}
setInit(true);
}
/**
* Called during Turbine destroy(). Persist the Manager state
* to disk
*/
public void shutdown() {
save();
}
/**
* Registers a new URL record. If the url is already registered in
* the system, doesn't modify the current record.
*
* @param url the url to register
*/
public void register( String url ) {
if ( url != null ) {
URLInfo info = getInfo( url );
if ( info == null ) {
register( new URLInfo( url, URLManagerService.STATUS_OK ) );
}
}
}
/**
* Registers a new URL record. If the url is already registered in
* the system, updates the status of this URL info record
*
* @param url the url to register
* @param status the status of this url
*/
public void register( String url, int status ) {
if ( url != null ) {
URLInfo info = getInfo( url );
if ( info == null ) {
register( new URLInfo( url, status ) );
} else {
info.setStatus( status );
}
}
}
/**
* Registers a new URL record. If the url is already registered in
* the system, updates both the status and the message of this URL
* info record
*
* @param url the url to register
* @param status the status of this url
* @param message a descriptive message of the status
*/
public void register( String url, int status, String message ) {
if ( url != null ) {
URLInfo info = getInfo( url );
if ( info == null ) {
register( new URLInfo( url, status, message ) );
} else {
info.setStatus( status );
info.setMessage( message );
}
}
}
/**
* Register or replace an URL record. All records are keyed to
* the imutable URL of URLInfo.
*
* @param info the info record to store
*/
public void register( URLInfo info ) {
if ( info != null) {
synchronized (urls) {
if( getInfo( info.getURL() ) == null )
urls.put( info.getURL().intern(), info );
}
}
}
/**
* Unregister an URL from the repository
*
* @param url the url to remove
*/
public void unregister( String url ) {
if ( url != null ) {
synchronized (urls) {
urls.remove( url.intern() );
}
}
}
/**
* Get the information record stored in the database about
* an URL.
*
* @param url the url whose record is sought
* @return the description record found in the repository or null.
*/
public URLInfo getInfo( String url ) {
URLInfo info = null;
if ( url != null ) {
synchronized(urls) {
info = (URLInfo)urls.get( url.intern() );
}
}
return info;
}
/**
* Test whether the URL is currently believed to be OK by this
* repository.
*
* @param url the url to be tested
* @return false is the url is known by this repository and has
* a status indicating an error, true otherwise.
*/
public boolean isOK( String url ) {
URLInfo info = getInfo( url );
// we don't know this URL, play it safe and say it's good
if ( info == null ) return true;
return ( info.getStatus() == URLManagerService.STATUS_OK );
}
/**
* List of the current known URLs in the repository
*
* @return a List of URL strings known to this repository
*/
public List list() {
synchronized (urls) {
return new Vector( urls.keySet() );
}
}
/**
* List of the current known URLs in the repository which have
* the given status.
*
* @param status the status to be retrieved. May be
* {@link URLManagerService#STATUS_ANY} to indicate any status
* @return a List of URL strings known to this repository with this status
*/
public List list( int status ) {
Vector result = new Vector();
synchronized (urls) {
Iterator i = urls.entrySet().iterator();
while( i.hasNext() ) {
Map.Entry entry = (Map.Entry)i.next();
URLInfo info = (URLInfo)entry.getValue();
if ( ( info.getStatus() & status ) != 0 ) {
result.addElement( entry.getKey() );
}
}
}
return result;
}
/**
* Load the persisted state of the repository from disk
*/
private synchronized void load() {
Map store = new HashMap();
Configuration config = null;
Log.info( "Restoring the URLs from disk: " + path );
try {
config = new Configuration( path );
int count = 1;
String url = null;
while ( ( url = ( config
.getString("entry."+count+".url") ) ) != null ) {
//Intern the url to ensure we can use "==" to compare
//and synchronize on it
url = url.intern();
int status = config.getInteger("entry."+count+".status", URLManagerService.STATUS_OK );
if( store.get( url ) == null )
store.put( url, new URLInfo( url, status ) );
count++;
}
Log.info( "URLManager loaded " + count + " urls" );
} catch ( Exception e ) {
Log.error( "Could not restore URLManager state", e );
return;
} finally {
// set the loaded store as the new store
this.urls = store;
}
}
/**
* Persist the state of the repository on disk in a properties file
*/
private synchronized void save() {
PrintWriter pw = null ;
try {
File propfile = new File(path); // FileWriter doesn't always do this
propfile.getParentFile().mkdirs();
propfile.createNewFile();
pw = new PrintWriter( new BufferedWriter( new FileWriter( propfile ) ) );
synchronized (urls) {
Iterator i = urls.values().iterator();
int entryNum = 1;
while( i.hasNext() ) {
URLInfo info = (URLInfo)i.next();
pw.print( "entry." );
pw.print( entryNum );
pw.print( ".url=" );
writeEscaped( pw, info.getURL() );
pw.println( "" );
pw.print( "entry." );
pw.print( entryNum );
pw.print( ".status=" );
pw.print( info.getStatus() );
pw.println( "" );
entryNum++;
}
}
} catch ( Throwable t ) {
Log.error( "Impossible to save URLManager state to "+path );
Log.error( t );
} finally {
if( pw != null )
{
pw.close();
}
}
}
/**
* Return the port of a proxy
* @param protocol The protocol that the proxy supports, e.g. 'http'
* @return The port number (1-65535), or -1 if no port was specified (= use default)
*/
public int getProxyPort( String protocol ) {
Integer proxyPort = (Integer)proxies.get( (protocol + ".port").toLowerCase() );
if (proxyPort != null)
return proxyPort.intValue();
else
return -1;
}
/**
* Return a proxy's hostname
* @param protocol The protocol that the proxy supports, e.g. 'http'
* @return The hostname of the proxy, or <code>null</code> if no proxy is specified for this protocol
*/
public String getProxyHost( String protocol ) {
String proxyHost = (String)proxies.get( (protocol + ".host").toLowerCase() );
return proxyHost;
}
/**
* <p>Escape values when saving.
* Appends a String to a StringBuffer, escaping commas.</p>
* <p>We assume that commas are unescaped.</p>
* @param sink a StringBuffer to write output
* @param element a value to be written
*/
protected void writeEscaped( PrintWriter sink, String element ) {
int upTo = element.indexOf(",");
if( upTo == -1 ) {
sink.print( element );
return;
}
sink.print( element.substring( 0, upTo ) );
sink.print( "\\," );
writeEscaped( sink, element.substring( upTo+1, element.length() ) );
return;
}
}