blob: fc8f5ff4800565d7af150c23f1189883e9dffe40 [file] [log] [blame]
/*
* $Id$
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.tiles.definition.dao;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.tiles.Definition;
import org.apache.tiles.Initializable;
import org.apache.tiles.TilesApplicationContext;
import org.apache.tiles.awareness.TilesApplicationContextAware;
import org.apache.tiles.definition.DefinitionsFactory;
import org.apache.tiles.definition.DefinitionsFactoryException;
import org.apache.tiles.definition.DefinitionsReader;
import org.apache.tiles.definition.RefreshMonitor;
import org.apache.tiles.definition.digester.DigesterDefinitionsReader;
import org.apache.tiles.impl.BasicTilesContainer;
import org.apache.tiles.reflect.ClassUtil;
/**
* Base abstract class for a DAO that is based on URLs and locale as a
* customization key.
*
* @version $Rev$ $Date$
* @since 2.1.0
*/
public abstract class BaseLocaleUrlDefinitionDAO implements
DefinitionDAO<Locale>, Initializable, TilesApplicationContextAware,
RefreshMonitor, URLReader {
/**
* The logging object.
*/
private static final Log LOG = LogFactory
.getLog(BaseLocaleUrlDefinitionDAO.class);
/**
* Compatibility constant.
*
* @deprecated use {@link DEFINITIONS_CONFIG} to avoid namespace collisions.
*/
private static final String LEGACY_DEFINITIONS_CONFIG = "definitions-config";
/**
* Contains the URL objects identifying where configuration data is found.
*
* @since 2.1.0
*/
protected List<URL> sourceURLs;
/**
* Contains the dates that the URL sources were last modified.
*
* @since 2.1.0
*/
protected Map<String, Long> lastModifiedDates;
/**
* The application context.
*
* @since 2.1.0
*/
protected TilesApplicationContext applicationContext;
/**
* Reader used to get definitions from the sources.
*
* @since 2.1.0
*/
protected DefinitionsReader reader;
/**
* Constructor.
*/
public BaseLocaleUrlDefinitionDAO() {
sourceURLs = new ArrayList<URL>();
lastModifiedDates = new HashMap<String, Long>();
}
/** {@inheritDoc}*/
public void setSourceURLs(List<URL> sourceURLs) {
this.sourceURLs = sourceURLs;
}
/** {@inheritDoc}*/
public void setReader(DefinitionsReader reader) {
this.reader = reader;
}
/** {@inheritDoc}*/
public void addSourceURL(URL sourceURL) {
if (sourceURLs == null) {
sourceURLs = new ArrayList<URL>();
}
sourceURLs.add(sourceURL);
}
/** {@inheritDoc} */
public void setApplicationContext(TilesApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
/** {@inheritDoc} */
public void init(Map<String, String> params) {
identifySources(params);
String readerClassName = params
.get(DefinitionsFactory.READER_IMPL_PROPERTY);
if (readerClassName != null) {
reader = (DefinitionsReader) ClassUtil.instantiate(readerClassName);
} else {
reader = new DigesterDefinitionsReader();
}
reader.init(params);
}
/** {@inheritDoc} */
public boolean refreshRequired() {
boolean status = false;
Set<String> urls = lastModifiedDates.keySet();
try {
for (String urlPath : urls) {
Long lastModifiedDate = lastModifiedDates.get(urlPath);
URL url = new URL(urlPath);
URLConnection connection = url.openConnection();
connection.connect();
long newModDate = connection.getLastModified();
if (newModDate != lastModifiedDate) {
status = true;
break;
}
}
} catch (Exception e) {
LOG.warn("Exception while monitoring update times.", e);
return true;
}
return status;
}
/**
* Detects the sources to load.
*
* @param initParameters The initialization parameters.
* @since 2.1.0
*/
protected void identifySources(Map<String, String> initParameters) {
if (applicationContext == null) {
throw new IllegalStateException(
"The TilesApplicationContext cannot be null");
}
String resourceString = getResourceString(initParameters);
String[] resources = getResourceNames(resourceString);
try {
for (int i = 0; i < resources.length; i++) {
Set<URL> urls = applicationContext.getResources(resources[i]);
if (urls != null && !urls.isEmpty()) {
for (URL resourceUrl : urls) {
if (resourceUrl != null) {
if (LOG.isDebugEnabled()) {
LOG.debug("Adding resource '" + resourceUrl
+ "' to definitions factory.");
}
String externalForm = resourceUrl.toExternalForm();
if (externalForm.indexOf('_', externalForm
.lastIndexOf("/")) < 0) {
sourceURLs.add(resourceUrl);
} else if (LOG.isDebugEnabled()) {
LOG.debug("Not adding resource '" + resourceUrl
+ "' to definitions factory because it is "
+ "supposed to be an internationalization.");
}
} else {
LOG.warn("Unable to find configured definition '"
+ resources[i] + "'");
}
}
} else {
LOG.warn("Unable to find resources under the name '"
+ resources[i] + "'");
}
}
} catch (IOException e) {
throw new DefinitionsFactoryException(
"Unable to parse definitions from " + resourceString, e);
}
}
/**
* Derive the resource string from the initialization parameters. If no
* parameter {@link DefinitionsFactory#DEFINITIONS_CONFIG} is available,
* attempts to retrieve {@link BasicTilesContainer#DEFINITIONS_CONFIG} and
* {@link #LEGACY_DEFINITIONS_CONFIG}. If neither are available, returns
* "/WEB-INF/tiles.xml".
*
* @param parms The initialization parameters.
* @return resource string to be parsed.
*/
@SuppressWarnings("deprecation")
protected String getResourceString(Map<String, String> parms) {
String resourceStr = parms.get(DefinitionsFactory.DEFINITIONS_CONFIG);
if (resourceStr == null) {
resourceStr = parms.get(BasicTilesContainer.DEFINITIONS_CONFIG);
}
if (resourceStr == null) {
resourceStr = parms.get(LEGACY_DEFINITIONS_CONFIG);
}
if (resourceStr == null) {
resourceStr = "/WEB-INF/tiles.xml";
}
return resourceStr;
}
/**
* Parse the resourceString into a list of resource paths which can be
* loaded by the application context.
*
* @param resourceString comma separated resources
* @return parsed resources
*/
protected String[] getResourceNames(String resourceString) {
return resourceString.split(",");
}
/**
* Loads definitions from an URL without loading from "parent" URLs.
*
* @param url The URL to read.
* @return The definition map that has been read.
*/
protected Map<String, Definition> loadDefinitionsFromURL(URL url) {
Map<String, Definition> defsMap = null;
try {
URLConnection connection = url.openConnection();
connection.connect();
lastModifiedDates.put(url.toExternalForm(), connection
.getLastModified());
// Definition must be collected, starting from the base
// source up to the last localized file.
defsMap = reader.read(connection.getInputStream());
} catch (FileNotFoundException e) {
// File not found. continue.
if (LOG.isDebugEnabled()) {
LOG.debug("File " + null + " not found, continue");
}
} catch (IOException e) {
throw new DefinitionsFactoryException(
"I/O error processing configuration.", e);
}
return defsMap;
}
}