Fixed problem with copying default theme to new theme's directory. Added Label as Widget attribute (but not evaluated by Mandalay theme engine). Still a NPE if theme evaluation detects an error, e.g. duplicate theme url.

git-svn-id: https://svn.libreccm.org/ccm/trunk@2681 8810af33-2d31-482b-a856-94f89814c4df
master
pb 2014-06-07 14:44:09 +00:00
parent e4823ae780
commit 6c272debdd
15 changed files with 635 additions and 342 deletions

View File

@ -65,7 +65,10 @@ import org.apache.log4j.Logger;
*/ */
public abstract class BaseApplicationServlet extends BaseServlet { public abstract class BaseApplicationServlet extends BaseServlet {
/** Logger instance for debugging purpose. */ /** Internal logger instance to faciliate debugging. Enable logging output
* by editing /WEB-INF/conf/log4j.properties int hte runtime environment
* and set com.arsdigita.web.BaseApplicationServlet=DEBUG by uncommenting
* or adding the line. */
private static final Logger s_log = Logger.getLogger(BaseApplicationServlet.class); private static final Logger s_log = Logger.getLogger(BaseApplicationServlet.class);
/** /**

View File

@ -34,13 +34,14 @@ import org.apache.log4j.LogManager;
import org.apache.log4j.PropertyConfigurator; import org.apache.log4j.PropertyConfigurator;
/** /**
* Web application lifecycle listener, used to perform central initialisation tasks at CCM startup * Web application lifecycle listener, used to perform central initialisation
* in a Servlet container / web application server, expecially setting the runtime context (file * tasks at CCM startup in a Servlet container / web application server,
* locations) and (in the future) the database connection. * expecially setting the runtime context (file locations) and (in the future)
* the database connection.
* *
* The methods of this classes are by definition only invoked by the Servlet container / web * The methods of this classes are by definition only invoked by the Servlet
* application server, not by any Servlet or java class of the application itself! Invocation is * container / web application server, not by any Servlet or java class of the
* managed by the deployment descriptor. * application itself! Invocation is managed by the deployment descriptor.
* *
* Note! Don't forget to configure it in web.xml deployment descriptor! * Note! Don't forget to configure it in web.xml deployment descriptor!
* <listener> * <listener>
@ -48,25 +49,31 @@ import org.apache.log4j.PropertyConfigurator;
* com.arsdigita.runtime.CCMApplicationContextListener * com.arsdigita.runtime.CCMApplicationContextListener
* </listener-class> * </listener-class>
* </listener> * </listener>
* According to the 2.3 specification these tags must be placed after the filter tags and before the * According to the 2.3 specification these tags must be placed after the
* Servlet tags! * filter tags and before the Servlet tags!
* *
* @author pboy * @author pboy
* @version $Id: $ * @version $Id: $
*/ */
public class CCMApplicationContextListener implements ServletContextListener { public class CCMApplicationContextListener implements ServletContextListener {
private static Logger s_log = Logger.getLogger(CCMApplicationContextListener.class); /** Internal logger instance to faciliate debugging. Enable logging output
* by editing /WEB-INF/conf/log4j.properties int hte runtime environment
* and set com.arsdigita.web.CCMApplicationContextListener=DEBUG by
* uncommenting or adding the line. */
private static final Logger s_log = Logger.getLogger(
CCMApplicationContextListener.class);
private static Runtime runtime; private static Runtime runtime;
/** /**
* Used to initialise classes at startup of the application, most of which needs to be plain * Used to initialise classes at startup of the application, most of which
* java objects (because they are also used by command line interface - installation, * needs to be plain java objects (because they are also used by command
* configuration, maintenance). * line interface - installation, configuration, maintenance).
* *
* Here we provide one of the two supported ways to bring up the CCM application. This handles * Here we provide one of the two supported ways to bring up the CCM
* the startup inside a Servlet container. The command line utilities handle the startup there. * application. This handles the startup inside a Servlet container.
* The command line utilities handle the startup there.
* Both initialise the same set of classes needed for CCM operations * Both initialise the same set of classes needed for CCM operations
* *
* @param applicationStartEvent * @param applicationStartEvent
@ -124,6 +131,7 @@ public class CCMApplicationContextListener implements ServletContextListener {
* *
* @param applicationEndEvent * @param applicationEndEvent
*/ */
@Override
public void contextDestroyed(ServletContextEvent applicationEndEvent) { public void contextDestroyed(ServletContextEvent applicationEndEvent) {
s_log.info("Shutdown procedure started."); s_log.info("Shutdown procedure started.");

View File

@ -115,8 +115,7 @@ public class Web {
/** /**
* Gets the servlet request object of the current thread. * Gets the servlet request object of the current thread.
* *
* @return The current <code>HttpServletRequest</code>; it can be * @return The current <code>HttpServletRequest</code>; it can be null
* null
*/ */
public static HttpServletRequest getRequest() { public static HttpServletRequest getRequest() {
return (HttpServletRequest) s_request.get(); return (HttpServletRequest) s_request.get();
@ -126,7 +125,6 @@ public class Web {
* Gets the servlet context of the current thread. * Gets the servlet context of the current thread.
* *
* @return The current <code>ServletContext</code>; it can be null * @return The current <code>ServletContext</code>; it can be null
*
*/ */
public static ServletContext getServletContext() { public static ServletContext getServletContext() {
return (ServletContext) s_servletContext.get(); return (ServletContext) s_servletContext.get();

View File

@ -65,7 +65,8 @@ public class ThemeDirectorConfig extends AbstractConfig {
return s_conf; return s_conf;
} }
// set of configuration parameters // /////////////////////////////////////////////////////////////////
// Set of Configuration Parameters
// ///////////////////////////////////////////////////////////////// // /////////////////////////////////////////////////////////////////
/** Directory that all of the default themes are copied from. */ /** Directory that all of the default themes are copied from. */
@ -74,19 +75,19 @@ public class ThemeDirectorConfig extends AbstractConfig {
("themedirector.default_theme_path", ("themedirector.default_theme_path",
Parameter.OPTIONAL, "/themes/master/"); Parameter.OPTIONAL, "/themes/master/");
/** Servlet context path containing the default theme. // /** Servlet context path containing the default theme.
* Previously ccm-themedirector used to be installed in its own // * Previously ccm-themedirector used to be installed in its own
* web context. In this case the appropriate web context should // * web context. In this case the appropriate web context should
* be specified. // * be specified.
* Currently, it is installed as part of the main application, // * Currently, it is installed as part of the main application,
* therefore it is empty by default. // * therefore it is empty by default.
* @deprecated without direct replacement. Themedirector's Webapp context // * @deprecated without direct replacement. Themedirector's Webapp context
* has to be determined at runtime. // * has to be determined at runtime.
*/ // */
private final Parameter m_defaultThemeContext = // private final Parameter m_defaultThemeContext =
new StringParameter // new StringParameter
("themedirector.default_theme_context", // ("themedirector.default_theme_context",
Parameter.OPTIONAL, ""); // Parameter.OPTIONAL, "");
// Parameter.OPTIONAL, "/ccm-themedirector/"); // Parameter.OPTIONAL, "/ccm-themedirector/");
/** File containing the default themes directory. Used in conjuntion with /** File containing the default themes directory. Used in conjuntion with
@ -143,10 +144,11 @@ public class ThemeDirectorConfig extends AbstractConfig {
*/ */
public ThemeDirectorConfig() { public ThemeDirectorConfig() {
register(m_fileExtParam);
register(m_defaultThemeContext);
register(m_defaultThemeManifest);
register(m_defaultThemePath); register(m_defaultThemePath);
// register(m_defaultThemeContext);
register(m_defaultThemeManifest);
register(m_fileExtParam);
register(m_themeDevFileWatchStartupDelay); register(m_themeDevFileWatchStartupDelay);
register(m_themeDevFileWatchPollDelay); register(m_themeDevFileWatchPollDelay);
register(m_themePubFileWatchStartupDelay); register(m_themePubFileWatchStartupDelay);
@ -155,6 +157,88 @@ public class ThemeDirectorConfig extends AbstractConfig {
loadInfo(); loadInfo();
} }
// /////////////////////////////////////////////////////////////////
// Set of Configuration Parameters
// /////////////////////////////////////////////////////////////////
/**
* Retrieves the path to a directory containing a complete set of theme
* files to copy into a new theme as a default implementation.
* By default it is set to a master directory containing the distribution's
* default theme. When creating a new theme it is copied over to provide
* a default for the new theme.
*
* Developer's note:
* Previously it was used as a string to filter a files directory stored in
* the Manifest file. Matching files were copied to the new theme's directory.
* The reason to use this approach is not documented. As a guess: it enables
* to copy from more than one directory, if those directories share a common
* name part which is specified here. But because all files must be present
* at deploy time to be included into the Manifest file it makes no sense.
* From original comment: "Specifically, if this is not null
* (or the empty string) than any file that is used as part of
* the default directory must start with this string."
*
* @return name of a directory containing a default theme implementation
*/
public String getDefaultThemePath() {
String defaultThemePath = (String)get(m_defaultThemePath);
if (defaultThemePath == null || defaultThemePath.trim().length() == 0) {
return null;
}
// remove leading slashwhich is already included in themedirector's
// constants of the directory layout.
if (defaultThemePath.startsWith("/")) {
defaultThemePath = defaultThemePath.substring(1);
}
return defaultThemePath;
}
// /**
// * This returns the name of the servlet context containing
// * the default theme.
// *
// * @return
// * @deprecated without direct replacement, See note above
// */
// public String getDefaultThemeContext() {
// String ctx = (String)get(m_defaultThemeContext);
// if (ctx == null) {
// ctx = "/";
// }
// if (!ctx.endsWith("/")) {
// ctx = ctx + "/";
// }
// if (!ctx.startsWith("/")) {
// ctx = "/" + ctx;
// }
// return ctx;
// }
/**
* This returns the name of the manifest file containing a list of default
* theme.
*
* @return
* @deprecated replaced by a direct copy from the default directory
*/
public String getDefaultThemeManifest() {
return (String)get(m_defaultThemeManifest);
}
private static final String DEFAULT_THEME_URL =
ThemeDirector.DEFAULT_THEME + "." + Theme.URL;
private static final String DEFAULT_THEME_URL_ATTRIBUTE =
"defaultThemeURLAttribute";
/**
* Purpose uncodumented.
*
* @return
*/
public Collection getDownloadFileExtensions() { public Collection getDownloadFileExtensions() {
if (m_downloadFileExtensions == null) { if (m_downloadFileExtensions == null) {
String extensions = (String)get(m_fileExtParam); String extensions = (String)get(m_fileExtParam);
@ -171,89 +255,7 @@ public class ThemeDirectorConfig extends AbstractConfig {
/** /**
* The number of seconds to wait before checking the database * Purpose undocumented.
* for the first time. A value of 0 means that the thread
* should not be started. This checks for published files.
*/
public Integer getThemePubFileWatchStartupDelay() {
return (Integer)get(m_themePubFileWatchStartupDelay);
}
/**
* Returns the number of seconds between checking for updated
* files in the file system. This checks for published files.
*/
public Integer getThemePubFileWatchPollDelay() {
return (Integer)get(m_themePubFileWatchPollDelay);
}
/**
* The number of seconds to wait before checking the database
* for the first time. A value of 0 means that the thread
* should not be started. This checks for development files.
*/
public Integer getThemeDevFileWatchStartupDelay() {
return (Integer)get(m_themeDevFileWatchStartupDelay);
}
/**
* Returns the number of seconds between checking for updated
* files in the file system. This checks for development files.
*/
public Integer getThemeDevFileWatchPollDelay() {
return (Integer)get(m_themeDevFileWatchPollDelay);
}
/**
* This returns the name of the servlet context containing
* the default theme
*/
public String getDefaultThemeContext() {
String ctx = (String)get(m_defaultThemeContext);
if (ctx == null) {
ctx = "/";
}
if (!ctx.endsWith("/")) {
ctx = ctx + "/";
}
if (!ctx.startsWith("/")) {
ctx = "/" + ctx;
}
return ctx;
}
/**
* This returns the name of the manifest file containing
* the default theme.
*/
public String getDefaultThemeManifest() {
return (String)get(m_defaultThemeManifest);
}
/**
* This returns a string that can be used as a file filter for
* the default directory. Specifically, if this is not null
* (or the empty string) than any file that is used as part of
* the default directory must start with this string
*/
public String getDefaultThemePath() {
String defaultThemePath = (String)get(m_defaultThemePath);
if (defaultThemePath == null || defaultThemePath.trim().length() == 0) {
return null;
}
if (defaultThemePath.startsWith("/")) {
defaultThemePath = defaultThemePath.substring(1);
}
return defaultThemePath;
}
private static final String DEFAULT_THEME_URL =
ThemeDirector.DEFAULT_THEME + "." + Theme.URL;
private static final String DEFAULT_THEME_URL_ATTRIBUTE =
"defaultThemeURLAttribute";
/**
* *
* @param req * @param req
* @return * @return
@ -285,4 +287,45 @@ public class ThemeDirectorConfig extends AbstractConfig {
req.setAttribute( DEFAULT_THEME_URL_ATTRIBUTE, themeURL ); req.setAttribute( DEFAULT_THEME_URL_ATTRIBUTE, themeURL );
return themeURL; return themeURL;
} }
/**
* The number of seconds to wait before checking the database
* for the first time. A value of 0 means that the thread
* should not be started. This checks for published files.
*
* @return
*/
public Integer getThemePubFileWatchStartupDelay() {
return (Integer)get(m_themePubFileWatchStartupDelay);
}
/**
* Returns the number of seconds between checking for updated
* files in the file system. This checks for published files.
*
* @return
*/
public Integer getThemePubFileWatchPollDelay() {
return (Integer)get(m_themePubFileWatchPollDelay);
}
/**
* The number of seconds to wait before checking the database
* for the first time. A value of 0 means that the thread
* should not be started. This checks for development files.
* @return
*/
public Integer getThemeDevFileWatchStartupDelay() {
return (Integer)get(m_themeDevFileWatchStartupDelay);
}
/**
* Returns the number of seconds between checking for updated
* files in the file system. This checks for development files.
* @return
*/
public Integer getThemeDevFileWatchPollDelay() {
return (Integer)get(m_themeDevFileWatchPollDelay);
}
} }

View File

@ -35,27 +35,29 @@ public interface ThemeDirectorConstants {
public final static String DEV_DIR_STUB = "/devel-themedir"; public final static String DEV_DIR_STUB = "/devel-themedir";
/** Path stub into directory for production themes (sub-dir of THEMES_DIR). /** Path stub into directory for production themes (sub-dir of THEMES_DIR).
* According to JavaEE spec with leading but without trailing "/"! */ * According to JavaEE spec with leading "/", but deviating from the
* JavaEE spec we add a trailing "/" for backwards compatibility to
* versions of CCM! */
public final static String public final static String
PROD_THEMES_BASE_DIR = THEMES_DIR + PROD_DIR_STUB; PROD_THEMES_BASE_DIR = THEMES_DIR + PROD_DIR_STUB + "/";
/** Path stub into directory for production themes (sub-dir of THEMES_DIR). /** Path stub into directory for production themes (sub-dir of THEMES_DIR).
* According to JavaEE spec with leading but without trailing "/"! */ * According to JavaEE spec with leading "/", but deviating from the
* JavaEE spec we add a trailing "/" for backwards compatibility to
* versions of CCM! */
public final static String public final static String
DEV_THEMES_BASE_DIR = THEMES_DIR + DEV_DIR_STUB ; DEV_THEMES_BASE_DIR = THEMES_DIR + DEV_DIR_STUB + "/";
// ccm-themedirector (formerly ccm-ldn-theme) is no longer installed in its // Developers NOTE:
// own web context (ROOT or ccm-ldn-theme/ccm-themedirector) so it is not // ================
// needed anymore. We we want to install it in its own context again, we // We should consider to use the theme's url as entered by the user with
// should find a way to determin the context from a central configuration. // a leading slash according to the specification and for sake of
// public final static String WEB_APP_NAME = "ROOT"; // consistency within CCM.
// We would have to adjust the validation listener and the process listener
// in class ui/ThemeForm and to update the existing themes in the database.
/** The location of the sync jsp used to sync up the multiple servers. */ /** The location of the sync jsp used to sync up the multiple servers. */
public final static String SYNC_JSP = "sync-theme.jsp"; public final static String SYNC_JSP = "sync-theme.jsp";
/** This can be used to find the root webapp directory that is used
by default for most of the applications in CCM */
public final static String ROOT_WEBAPP_PATH = "/ROOT";
public static final String THEME_XML_PREFIX = "theme:"; public static final String THEME_XML_PREFIX = "theme:";
public final static String XML_NS = public final static String XML_NS =
"http://ccm.redhat.com/themedirector/1.0"; "http://ccm.redhat.com/themedirector/1.0";

View File

@ -32,3 +32,9 @@ theme.undo.default_style=Default Style
theme.none=None theme.none=None
theme.save=Save theme.save=Save
theme.set_default_theme=Default Theme theme.set_default_theme=Default Theme
theme.title_hint=Enter the title of the theme, up to 80 characters.
theme.description_hint=Enter a short description for the theme, up to 4000 characters.
theme.url_hint=Enter the LAST part of the url for the theme, eg 'holiday'. Should NOT include a leading nor a trailing slash!
theme.save_button_hint=Save the details in the form
theme.cancel_button_hint=Abort changes & reset the form.
theme.cancel_button_pressed_msg=cancel pressed

View File

@ -32,3 +32,9 @@ theme.undo.default_style=Standard-Theme
theme.none=Keines theme.none=Keines
theme.save=Speichern theme.save=Speichern
theme.set_default_theme=Standard-Theme theme.set_default_theme=Standard-Theme
theme.title_hint=Der Titel des Themes, kann bis zu 80 Zeichen lang sein.
theme.description_hint=Geben Sie eine kurze Beschreibung ein, bis zu 4000 Zeichen lang.
theme.url_hint=Geben Sie den LETZTEN Teil der URL ein, z.B. 'ferien'. Der Eintrag darf weder ein f\u00fchrendes noch ein abschlie\u00dfendes '/' enthalten!
theme.save_button_hint=Speichern der Angaben in dem Formular.
theme.cancel_button_hint=Verwerfen der Eintragungen und R\u00fccksetzen des Formulars.
theme.cancel_button_pressed_msg=Vorgang abgebrochen

View File

@ -32,3 +32,9 @@ theme.undo.default_style=Default Style
theme.none=None theme.none=None
theme.save=Save theme.save=Save
theme.set_default_theme=Default Theme theme.set_default_theme=Default Theme
theme.title_hint=Enter the title of the theme, up to 80 characters.
theme.description_hint=Enter a short description for the theme, up to 4000 characters.
theme.url_hint=Enter the LAST part of tht url for the theme, eg 'holiday'. Should NOT include a leading nor a trailing slash!
theme.save_button_hint=Save the details in the form
theme.cancel_button_hint=Abort changes & reset the form.
theme.cancel_button_pressed_msg=cancel pressed

View File

@ -32,3 +32,9 @@ theme.undo.default_style=Default Style
theme.none=None theme.none=None
theme.save=Save theme.save=Save
theme.set_default_theme=Default Theme theme.set_default_theme=Default Theme
theme.title_hint=Enter the title of the theme, up to 80 characters.
theme.description_hint=Enter a short description for the theme, up to 4000 characters.
theme.url_hint=Enter the LAST part of tht url for the theme, eg 'holiday'. Should NOT include a leading nor a trailing slash!
theme.save_button_hint=Save the details in the form
theme.cancel_button_hint=Abort changes & reset the form.
theme.cancel_button_pressed_msg=cancel pressed

View File

@ -52,6 +52,8 @@ import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.DirectoryFileFilter;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
@ -64,50 +66,68 @@ import org.apache.log4j.Logger;
*/ */
public class ThemeForm extends Form implements Cancellable, ThemeDirectorConstants { public class ThemeForm extends Form implements Cancellable, ThemeDirectorConstants {
/** Internal logger instance to faciliate debugging. Enable logging output
* by editing /WEB-INF/conf/log4j.properties int hte runtime environment
* and set com.arsdigita.themedirector.ui.ThemeForm=DEBUG by uncommenting
* or adding the line. */
private static final Logger s_log = Logger.getLogger(ThemeForm.class); private static final Logger s_log = Logger.getLogger(ThemeForm.class);
private ThemeSelectionModel m_theme; private final ThemeSelectionModel m_theme;
private TextField m_title; private final TextField m_title;
private TextArea m_description; private final TextArea m_description;
private TextField m_url; private final TextField m_url;
private SaveCancelSection m_buttons; private final SaveCancelSection m_buttons;
/**
* Constructor creats the input form to create a new theme or edit an
* existing one.
*
* @param name
* @param theme
*/
public ThemeForm(String name, public ThemeForm(String name,
ThemeSelectionModel theme) { ThemeSelectionModel theme) {
super(name, new GridPanel(2)); super(name, new GridPanel(2));
setClassAttr("simpleThemeForm"); setClassAttr("simpleThemeForm");
setRedirecting(true); setRedirecting(true);
m_theme = theme; m_theme = theme; // Initialize ThemeSelectionModel
// Add the Title input field
add(new Label(GlobalizationUtil.globalize("theme.title"))); add(new Label(GlobalizationUtil.globalize("theme.title")));
m_title = new TextField(new StringParameter("title")); m_title = new TextField(new StringParameter("title"));
// Experimental. We are migrating the Label if a widget as part of the
// widgets's xml properties.
m_title.setLabel(GlobalizationUtil.globalize("theme.title"));
m_title.addValidationListener(new NotEmptyValidationListener()); m_title.addValidationListener(new NotEmptyValidationListener());
m_title.setHint("Enter the title of the theme, up to 80 characters"); m_title.setHint(GlobalizationUtil.globalize("theme.title_hint"));
m_title.setSize(40); m_title.setSize(40);
add(m_title); add(m_title);
add(new Label(GlobalizationUtil.globalize("theme.description"))); add(new Label(GlobalizationUtil.globalize("theme.description")));
m_description = new TextArea(new StringParameter("description")); m_description = new TextArea(new StringParameter("description"));
// Experimental, see above
m_description.setLabel(GlobalizationUtil.globalize("theme.description"));
m_description.setCols(40); m_description.setCols(40);
m_description.setRows(4); m_description.setRows(4);
m_description.setHint( m_description.setHint(GlobalizationUtil
"Enter a short description for the theme, up to 4000 characters" .globalize("theme.description_hint"));
);
add(m_description); add(m_description);
add(new Label(GlobalizationUtil.globalize("theme.url"))); add(new Label(GlobalizationUtil.globalize("theme.url")));
m_url = new TextField(new StringParameter("url")); m_url = new TextField(new StringParameter("url"));
// Experimental, see above
m_url.setLabel(GlobalizationUtil.globalize("theme.url"));
m_url.addValidationListener(new NotEmptyValidationListener()); m_url.addValidationListener(new NotEmptyValidationListener());
m_title.setSize(40); m_title.setSize(40);
m_url.setHint( m_url.setHint(GlobalizationUtil.globalize("theme.url_hint"));
"Enter the url for the theme, eg 'holiday'"
);
add(m_url); add(m_url);
m_buttons = new SaveCancelSection(); m_buttons = new SaveCancelSection();
m_buttons.getSaveButton().setHint("Save the details in the form"); m_buttons.getSaveButton().setHint(GlobalizationUtil
m_buttons.getCancelButton().setHint("Abort changes & reset the form"); .globalize("theme.save_button_hint"));
m_buttons.getCancelButton().setHint(GlobalizationUtil
.globalize("theme.cancel_button_hint"));
add(m_buttons); add(m_buttons);
addSubmissionListener(new ThemeSubmissionListener()); addSubmissionListener(new ThemeSubmissionListener());
@ -116,23 +136,47 @@ public class ThemeForm extends Form implements Cancellable, ThemeDirectorConstan
addValidationListener(new ThemeValidationListener()); addValidationListener(new ThemeValidationListener());
} }
// if this form is cancelled /**
* Processed if this form is cancelled.
*
* @param s
* @return
*/
@Override
public boolean isCancelled(PageState s) { public boolean isCancelled(PageState s) {
return m_buttons.getCancelButton().isSelected(s); return m_buttons.getCancelButton().isSelected(s);
} }
/**
*
*/
private class ThemeSubmissionListener implements FormSubmissionListener { private class ThemeSubmissionListener implements FormSubmissionListener {
/**
*
* @param e
* @throws FormProcessException
*/
@Override
public void submitted(FormSectionEvent e) public void submitted(FormSectionEvent e)
throws FormProcessException { throws FormProcessException {
PageState state = e.getPageState(); PageState state = e.getPageState();
if (m_buttons.getCancelButton().isSelected(state)) { if (m_buttons.getCancelButton().isSelected(state)) {
throw new FormProcessException("cancel pressed"); throw new FormProcessException(
"cancel pressed",
GlobalizationUtil.globalize("theme.cancel_button_hint")
);
} }
} }
} }
/**
* Initializes the theme form with appropriate values if theme already
* exists.
*/
private class ThemeInitListener implements FormInitListener { private class ThemeInitListener implements FormInitListener {
@Override
public void init(FormSectionEvent e) public void init(FormSectionEvent e)
throws FormProcessException { throws FormProcessException {
PageState state = e.getPageState(); PageState state = e.getPageState();
@ -150,15 +194,34 @@ public class ThemeForm extends Form implements Cancellable, ThemeDirectorConstan
} }
} }
/**
* ProcessListener class to act upon the themedirector form input (after
* successful input validation if any). It's process method is the entry
* point.
*/
private class ThemeProcessListener implements FormProcessListener { private class ThemeProcessListener implements FormProcessListener {
/**
* Process the form input data. The data are first stored into the
* database and than the file system is synced if required. In case of
* a new theme the default theme files (if existent) are copied. If for
* an existing theme the name (url) has changed, the filesystem
* directories are modified accordingly.
*
* @param e
* @throws FormProcessException
*/
@Override
public void process(FormSectionEvent e) public void process(FormSectionEvent e)
throws FormProcessException { throws FormProcessException {
PageState state = e.getPageState(); PageState state = e.getPageState();
Theme theme = m_theme.getSelectedTheme(state); Theme theme = m_theme.getSelectedTheme(state);
String oldURL = null; String oldURL = null;
String newURL = null; String newURL = null;
if (theme == null) { if (theme == null) {
/* We handle a new (created) theme. No previous values exist.*/
newURL = (String)m_url.getValue(state); newURL = (String)m_url.getValue(state);
theme = new Theme((String)m_title.getValue(state), theme = new Theme((String)m_title.getValue(state),
(String)m_description.getValue(state), (String)m_description.getValue(state),
@ -171,7 +234,7 @@ public class ThemeForm extends Form implements Cancellable, ThemeDirectorConstan
theme.setURL(newURL); theme.setURL(newURL);
} }
// only add the theme if it is published // only add to the theme if it is published
if (theme.getLastPublishedUser() != null) { if (theme.getLastPublishedUser() != null) {
Subsite.getConfig().addTheme(theme.getURL(), theme.getTitle()); Subsite.getConfig().addTheme(theme.getURL(), theme.getTitle());
} }
@ -180,24 +243,23 @@ public class ThemeForm extends Form implements Cancellable, ThemeDirectorConstan
Subsite.getConfig().removeTheme(oldURL); Subsite.getConfig().removeTheme(oldURL);
} }
theme.save(); theme.save(); // save theme to database
m_theme.setSelectedObject(state, theme); m_theme.setSelectedObject(state, theme);
// now that the db part is done, we do the file IO // now that the db part is done, we do the file IO
File newDirectory = null; File newDirectory = null;
File oldDirectory = null; File oldDirectory = null;
try { try {
// The WebAppRoot should be something like this: // Determine the WebAppRoot should be something like this:
// /var/ccm-devel/web/<username>/<projectname>/webapps/ccm-ldn-theme; // /var/ccm-devel/web/<username>/<projectname>/webapps/libreccm;
File currentRoot = new File(Web.getServletContext().getRealPath("/")); File currentRoot = new File(Web.getServletContext().getRealPath("/"));
newDirectory = new File(currentRoot, DEV_THEMES_BASE_DIR + newDirectory = new File(currentRoot, DEV_THEMES_BASE_DIR +
newURL); newURL);
if (newDirectory.exists() && !newURL.equals(oldURL)) { if (newDirectory.exists() && !newURL.equals(oldURL)) {
// this means there is a file in the file system // this means there is a file in the file system but not in
// but not in the database // the database this should never happen because "validate"
// this should never happen because "validate" should // should catch it.
// catch it.
throw new UncheckedWrapperException throw new UncheckedWrapperException
("The file " + newDirectory.getName() + " already " + ("The file " + newDirectory.getName() + " already " +
"exists in the file system but not in the " + "exists in the file system but not in the " +
@ -211,15 +273,16 @@ public class ThemeForm extends Form implements Cancellable, ThemeDirectorConstan
} }
if ( oldURL == null || !oldDirectory.exists()) { if ( oldURL == null || !oldDirectory.exists()) {
// we make sure that the base directory exists and // we make sure that the base directory exists and then we
// then we copy the files over. // copy the files over.
File baseDirectory = new File(currentRoot, File baseDirectory = new File(currentRoot,
DEV_THEMES_BASE_DIR); DEV_THEMES_BASE_DIR);
if (!baseDirectory.exists()) { if (!baseDirectory.exists()) {
baseDirectory.mkdirs(); baseDirectory.mkdirs();
} }
copyDefaultFiles(newDirectory); copyDefaultTheme(newDirectory,null);
// copyDefaultFiles(newDirectory);
if (oldDirectory != null && !oldDirectory.exists()) { if (oldDirectory != null && !oldDirectory.exists()) {
s_log.warn("We were asked to move files from " + s_log.warn("We were asked to move files from " +
@ -251,8 +314,9 @@ public class ThemeForm extends Form implements Cancellable, ThemeDirectorConstan
// the old directory...we need them to point to the // the old directory...we need them to point to the
// new directory // new directory
DataCollection collection = DataCollection collection =
SessionManager.getSession().retrieve SessionManager
(Site.BASE_DATA_OBJECT_TYPE); .getSession()
.retrieve(Site.BASE_DATA_OBJECT_TYPE);
collection.addEqualsFilter(Site.STYLE_DIRECTORY, collection.addEqualsFilter(Site.STYLE_DIRECTORY,
oldURL); oldURL);
while (collection.next()) { while (collection.next()) {
@ -270,10 +334,23 @@ public class ThemeForm extends Form implements Cancellable, ThemeDirectorConstan
} }
} }
/**
* ValöidatgionListener class to check the themedirector form input data.
* It's validate method is the entry point and executed when submitting
* the form.
*/
private class ThemeValidationListener implements FormValidationListener { private class ThemeValidationListener implements FormValidationListener {
/**
*
* @param e
* @throws FormProcessException
*/
@Override
public void validate(FormSectionEvent e) public void validate(FormSectionEvent e)
throws FormProcessException { throws FormProcessException {
PageState state = e.getPageState(); PageState state = e.getPageState();
String url = (String)m_url.getValue(state); String url = (String)m_url.getValue(state);
validateURLForm(state, url); validateURLForm(state, url);
validateURLUniqueness(state, url); validateURLUniqueness(state, url);
@ -298,6 +375,9 @@ public class ThemeForm extends Form implements Cancellable, ThemeDirectorConstan
/** /**
* This checks the form of the url...specifically, we are only allowing * This checks the form of the url...specifically, we are only allowing
* [A-Z,a-z,0-9,_,-]. * [A-Z,a-z,0-9,_,-].
* @param state
* @param url
* @throws com.arsdigita.bebop.FormProcessException
*/ */
public void validateURLForm(PageState state, String url) public void validateURLForm(PageState state, String url)
throws FormProcessException { throws FormProcessException {
@ -316,10 +396,11 @@ public class ThemeForm extends Form implements Cancellable, ThemeDirectorConstan
} }
} }
/** /**
* This makes sure no other theme has the same URL * This makes sure no other theme has the same URL
* @param state
* @param url
* @throws com.arsdigita.bebop.FormProcessException
*/ */
public void validateURLUniqueness(PageState state, String url) public void validateURLUniqueness(PageState state, String url)
throws FormProcessException { throws FormProcessException {
@ -341,10 +422,39 @@ public class ThemeForm extends Form implements Cancellable, ThemeDirectorConstan
} }
} }
/**
* Copies a complete directory containing the default theme to a newly
* created theme's directory without any filtering or other processing.
* It assumes, that the source directory contains a complete and working
* set of theme files.
*
* @param newThemeDirectory specifies the target directory. Must not
* be null.
* @param defaultThemeDirectory Directory containing a complete set of
* theme files intended as default theme.
* If null the default theme directory is
* retrieved from ThemeDirector config
*
* @throws IOException
*/
private void copyDefaultTheme(File newThemeDirectory,
File defaultThemeDirectory) throws IOException {
if (defaultThemeDirectory == null) {
defaultThemeDirectory = new File(
Web.getServletContext().getRealPath("/")
+ ThemeDirector.getConfig().getDefaultThemePath());
}
FileUtils.copyDirectory(defaultThemeDirectory,
newThemeDirectory);
}
/** /**
* This copies the default theme files to the new directory that * Copies the default theme files to the new directory using a
* is specified by the pass in File * Manifest file to determine the files to copy.
*
* @param newDirectory specifies the target directory
* @throws IOException
*/ */
private void copyDefaultFiles(File newDirectory) throws IOException { private void copyDefaultFiles(File newDirectory) throws IOException {
ClassLoader loader = Thread.currentThread().getContextClassLoader(); ClassLoader loader = Thread.currentThread().getContextClassLoader();
@ -358,7 +468,8 @@ public class ThemeForm extends Form implements Cancellable, ThemeDirectorConstan
} }
if ( !newDirectory.mkdirs() ) { if ( !newDirectory.mkdirs() ) {
throw new UncheckedWrapperException("Cannot create theme directory "+newDirectory.getAbsolutePath()); throw new UncheckedWrapperException("Cannot create theme directory "
+newDirectory.getAbsolutePath());
} }
ManifestReader reader = ManifestReader reader =
@ -367,10 +478,19 @@ public class ThemeForm extends Form implements Cancellable, ThemeDirectorConstan
} }
/**
*
*/
private class FileWriterManifestReader extends ManifestReader { private class FileWriterManifestReader extends ManifestReader {
private File m_newDirectory; private final File m_newDirectory;
private String m_directoryFilter; private final String m_directoryFilter;
/**
* Constructor.
*
* @param stream
* @param newDirectory
*/
FileWriterManifestReader(InputStream stream, File newDirectory) { FileWriterManifestReader(InputStream stream, File newDirectory) {
super(stream); super(stream);
m_newDirectory = newDirectory; m_newDirectory = newDirectory;
@ -378,6 +498,13 @@ public class ThemeForm extends Form implements Cancellable, ThemeDirectorConstan
m_directoryFilter = ThemeDirector.getConfig().getDefaultThemePath(); m_directoryFilter = ThemeDirector.getConfig().getDefaultThemePath();
} }
/**
*
* @param is
* @param filePath
* @param isStyleFile
*/
@Override
public void processManifestFileLine(InputStream is, public void processManifestFileLine(InputStream is,
String filePath, String filePath,
boolean isStyleFile) { boolean isStyleFile) {

View File

@ -42,6 +42,7 @@ import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
import com.arsdigita.templating.XSLTemplate; import com.arsdigita.templating.XSLTemplate;
import com.arsdigita.templating.WrappedTransformerException; import com.arsdigita.templating.WrappedTransformerException;
import java.net.MalformedURLException;
import javax.xml.transform.ErrorListener; import javax.xml.transform.ErrorListener;
import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerException;
@ -49,14 +50,19 @@ import javax.xml.transform.TransformerException;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
/** /**
* This displays information about the results of running a validation * This displays information about the results of running a validation test
* test on all of the stylesheets for a given theme. It also includes * on all of the stylesheets for a given theme. It also includes
* links to "revalidate" and to return the viewing the theme. * links to "revalidate" and to return the viewing the theme.
* *
* @author Randy Graebner &lt;randyg@redhat.com&gt; * @author Randy Graebner &lt;randyg@redhat.com&gt;
*/ */
class ThemeValidationPanel extends GridPanel implements ThemeDirectorConstants { class ThemeValidationPanel extends GridPanel implements ThemeDirectorConstants {
/** Internal logger instance to faciliate debugging. Enable logging output
* by editing /WEB-INF/conf/log4j.properties int the runtime environment
* and set
* com.arsdigita.themedirector.ui.ThemeValidationPanel=DEBUG
* by uncommenting or adding the line. */
private static final Logger s_log = private static final Logger s_log =
Logger.getLogger(ThemeValidationPanel.class); Logger.getLogger(ThemeValidationPanel.class);
@ -65,13 +71,15 @@ class ThemeValidationPanel extends GridPanel implements ThemeDirectorConstants {
RequestLocal m_listener; RequestLocal m_listener;
/** /**
* This creates a new validation panel * This creates a new validation panel.
*
* @param model This is the selection model so that the panel knows * @param model This is the selection model so that the panel knows
* which theme to operation on * which theme to operation on
* @param container This is the parent container that holds this theme. * @param container This is the parent container that holds this theme.
* When the user wants to quit validation and return to where they were, * When the user wants to quit validation and return
* the visibility of the passed in container inverted (if it is visible * to where they were, the visibility of the passed in
* then it becomes invisible, if it is invisible, it becomes visible) and * container inverted (if it is visible then it becomes
* invisible, if it is invisible, it becomes visible) and
* this item becomes invisible. * this item becomes invisible.
*/ */
// TODO: passing in the container is a hackish way to do the visibility... // TODO: passing in the container is a hackish way to do the visibility...
@ -80,8 +88,8 @@ class ThemeValidationPanel extends GridPanel implements ThemeDirectorConstants {
SimpleComponent parentContainer) { SimpleComponent parentContainer) {
super(1); super(1);
m_model = model; m_model = model;
Label results = Label results = new Label(GlobalizationUtil
new Label(GlobalizationUtil.globalize("theme.validation_results")); .globalize("theme.validation_results"));
results.setFontWeight(Label.BOLD); results.setFontWeight(Label.BOLD);
add(results); add(results);
m_listener = new RequestLocal(); m_listener = new RequestLocal();
@ -92,14 +100,16 @@ class ThemeValidationPanel extends GridPanel implements ThemeDirectorConstants {
new ActionLink(new Label(GlobalizationUtil.globalize new ActionLink(new Label(GlobalizationUtil.globalize
("theme.revalidate_theme"))); ("theme.revalidate_theme")));
revalidateLink.addActionListener(new ActionListener() { revalidateLink.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
validateStylesheets(e.getPageState()); validateStylesheets(e.getPageState());
} }
}); });
add(revalidateLink); add(revalidateLink);
ToggleActionLink returnLink = new ToggleActionLink ToggleActionLink returnLink = new ToggleActionLink(new Label(
(new Label(GlobalizationUtil.globalize("theme.return_to_previous"))); GlobalizationUtil
.globalize("theme.return_to_previous")));
returnLink.addToggleComponent(this); returnLink.addToggleComponent(this);
returnLink.addToggleComponent(parentContainer); returnLink.addToggleComponent(parentContainer);
add(returnLink); add(returnLink);
@ -112,7 +122,7 @@ class ThemeValidationPanel extends GridPanel implements ThemeDirectorConstants {
* class but this is more clear as to what is being done. * class but this is more clear as to what is being done.
*/ */
private class ToggleActionLink extends ActionLink implements ActionListener { private class ToggleActionLink extends ActionLink implements ActionListener {
private ArrayList m_components; private final ArrayList m_components;
ToggleActionLink(Label name) { ToggleActionLink(Label name) {
super(name); super(name);
@ -120,6 +130,7 @@ class ThemeValidationPanel extends GridPanel implements ThemeDirectorConstants {
addActionListener(this); addActionListener(this);
} }
@Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
Iterator iter = m_components.iterator(); Iterator iter = m_components.iterator();
PageState state = e.getPageState(); PageState state = e.getPageState();
@ -150,34 +161,44 @@ class ThemeValidationPanel extends GridPanel implements ThemeDirectorConstants {
/** /**
* This method sets up the validation by finding the correct base * This method sets up the validation by finding the correct base
* directory and setting up other necessary initialization variables * directory and setting up other necessary initialization variables.
*
* @param state
* @param listener
*/ */
private boolean validateStylesheets(PageState state, private boolean validateStylesheets(PageState state,
LoggingErrorListener listener) { LoggingErrorListener listener) {
// this should verify the stylesheets and then present
// any error messages that are found /* Determine the theme to check */
Theme theme = m_model.getSelectedTheme(state); Theme theme = m_model.getSelectedTheme(state);
// The call to resolve returns a url similar to this: /* Determine the location in the servers file system */
// http://localhost:9008/resource/ccm-ldn-theme,ROOT/themes/heirloom/apps/theme/xsl/index.xs
//
// TODO: This is VERY UGLY! Bad style to code a path into source code!
// String base = "http://" + Web.getConfig().getHost().toString() +
// "/resource/ccm-ldn-theme/";
String base = "http://" + Web.getConfig().getHost().toString() +
"/resource/ROOT/";
File currentRoot = new File(Web.getServletContext().getRealPath("/")); File currentRoot = new File(Web.getServletContext().getRealPath("/"));
File devDir = new File(currentRoot, DEV_THEMES_BASE_DIR + File devDir = new File(currentRoot,
theme.getURL()); DEV_THEMES_BASE_DIR + theme.getURL() );
// TODO: There has to be a better way to do this /* Determine the URL to the stylesheets. Usually the URL is determined
String stylesheetPath = base + by the templating system, based on a stylesheetPath.txt file
DEV_THEMES_BASE_DIR + theme.getURL(); containing patterns to search for.
Developer's Note:
We used to use a 'resource' tag involing a resource servlet to
deliver the correct file either from database or filesystem. Ir would
require an URL similar to
http://localhost:9008/libreccm/resource/themes/heirloom/apps/theme/xsl/index.xs
where librecms is the context ccm happpens to be installed in.
Currently we bypass the resource servlet and access the filesystem
directly. Must be modified as soon as we deliver the theme from db. */
String stylesheetPath = "http://" + Web.getConfig().getHost().toString()
+ Web.getWebappContextPath()
+ DEV_THEMES_BASE_DIR + theme.getURL() ;
if (s_log.isDebugEnabled()) { if (s_log.isDebugEnabled()) {
s_log.debug("Path is " + stylesheetPath); s_log.debug("Path is " + stylesheetPath);
} }
// this should verify the stylesheets and then present
// any error messages that are found
checkFiles(devDir, stylesheetPath, listener); checkFiles(devDir, stylesheetPath, listener);
return !listener.hasErrors(); return !listener.hasErrors();
@ -189,7 +210,8 @@ class ThemeValidationPanel extends GridPanel implements ThemeDirectorConstants {
* the entire folder subtree, loading every single available xsl * the entire folder subtree, loading every single available xsl
* file. * file.
*/ */
private void checkFiles(File baseDirectory, String basePath, private void checkFiles(File baseDirectory,
String basePath,
ErrorListener listener) { ErrorListener listener) {
File[] list = baseDirectory.listFiles(new XSLFileFilter()); File[] list = baseDirectory.listFiles(new XSLFileFilter());
if (list == null) { if (list == null) {
@ -203,14 +225,10 @@ class ThemeValidationPanel extends GridPanel implements ThemeDirectorConstants {
// future caught errors. This is sort of a hack but I am not // future caught errors. This is sort of a hack but I am not
// sure of a better way around it // sure of a better way around it
boolean transformUsesListener = true; boolean transformUsesListener = true;
for (File list1 : list) {
for (int i = 0; i < list.length; i ++) { if (list1.isDirectory()) {
if (list[i].isDirectory()) {
// Only check top level XSL - the rest are xsl:import'd
//checkFiles(list[i], basePath + "/" + list[i].getName(),
// listener);
} else { } else {
String filePath = basePath + "/" + list[i].getName(); String filePath = basePath + "/" + list1.getName();
try { try {
URL stylesheetURL = new URL(filePath); URL stylesheetURL = new URL(filePath);
if (s_log.isDebugEnabled()) { if (s_log.isDebugEnabled()) {
@ -237,9 +255,9 @@ class ThemeValidationPanel extends GridPanel implements ThemeDirectorConstants {
} }
} }
s_log.debug("Wrapper excpetion thrown"); s_log.debug("Wrapper excpetion thrown");
} catch (Exception exp) { } catch (MalformedURLException exp) {
s_log.warn("Error creating template that was not a " + s_log.warn("Error creating template that was not a "
" standard wrapper transformer exception.", + "standard wrapper transformer exception.",
exp); exp);
} }
} }
@ -249,9 +267,10 @@ class ThemeValidationPanel extends GridPanel implements ThemeDirectorConstants {
/** /**
* This is simply used so that only directories and xsl files are * This is simply used so that only directories and xsl files are
* examined * examined.
*/ */
private static class XSLFileFilter implements FileFilter { private static class XSLFileFilter implements FileFilter {
@Override
public boolean accept(File pathname) { public boolean accept(File pathname) {
return pathname.isDirectory() || return pathname.isDirectory() ||
pathname.getName().endsWith(".xsl"); pathname.getName().endsWith(".xsl");
@ -260,8 +279,8 @@ class ThemeValidationPanel extends GridPanel implements ThemeDirectorConstants {
private class ValidationResults extends SimpleContainer { private class ValidationResults extends SimpleContainer {
private Label m_noErrorsLabel; private final Label m_noErrorsLabel;
private Label m_errorsLabel; private final Label m_errorsLabel;
ValidationResults() { ValidationResults() {
super(); super();
@ -299,8 +318,8 @@ class ThemeValidationPanel extends GridPanel implements ThemeDirectorConstants {
/** /**
* if the collection size > 0 then it prints out the xml * If the collection size > 0 then it prints out the xml
* to display the messages * to display the messages.
*/ */
private void printMessages(String name, Element parent, private void printMessages(String name, Element parent,
Collection messages) { Collection messages) {

View File

@ -46,21 +46,38 @@ import org.apache.log4j.Logger;
/** /**
* This approves the theme and pushes it to the production file location * This approves the theme and pushes it to the production file location
* This action means that the user wants to approve the themes and * This action means that the user wants to approve the themes and
* push them live. This is done by copying the files from * push them live. This is done by copying the files from the devel directory
* into the published directory.
* *
* @author Randy Graebner &lt;randyg@redhat.com&gt; * @author Randy Graebner &lt;randyg@redhat.com&gt;
*/ */
public class ApproveThemeActionListener implements ThemeDirectorConstants, ActionListener { public class ApproveThemeActionListener implements ThemeDirectorConstants,
ActionListener {
private static final Logger s_log = /** Internal logger instance to faciliate debugging. Enable logging output
Logger.getLogger(ApproveThemeActionListener.class); * by editing /WEB-INF/conf/log4j.properties int the runtime environment
* and set
* com.arsdigita.themedirector.ui.listeners.ApproveThemeActionListener=DEBUG
* by uncommenting or adding the line. */
private static final Logger s_log = Logger.getLogger(
ApproveThemeActionListener.class);
private ThemeSelectionModel m_model; private final ThemeSelectionModel m_model;
/**
* Constructor, just stores the ThemeSelectionModel.
*
* @param model the ThemeSelectionModel
*/
public ApproveThemeActionListener(ThemeSelectionModel model) { public ApproveThemeActionListener(ThemeSelectionModel model) {
m_model = model; m_model = model;
} }
/**
*
* @param e
*/
@Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
// First, we rename the current production directory // First, we rename the current production directory
// so that if there is an exception, we can try to move it // so that if there is an exception, we can try to move it

View File

@ -19,32 +19,37 @@
package com.arsdigita.themedirector.util; package com.arsdigita.themedirector.util;
import com.arsdigita.themedirector.ThemeDirector;
import com.arsdigita.themedirector.ThemeDirectorConstants;
import com.arsdigita.util.UncheckedWrapperException;
import com.arsdigita.web.Web;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.IOException; import java.io.IOException;
import java.io.LineNumberReader; import java.io.LineNumberReader;
import java.util.HashMap;
import com.arsdigita.web.Web;
import com.arsdigita.util.UncheckedWrapperException;
import java.util.Collection; import java.util.Collection;
import com.arsdigita.themedirector.ThemeDirector; import java.util.HashMap;
import com.arsdigita.themedirector.ThemeDirectorConstants;
import javax.servlet.ServletContext; import javax.servlet.ServletContext;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
/** /**
* This is a utility class that will take in a manifest file and * This is a utility class that will take in a manifest file, read it, and
* read it and then make calls to methods for each file that is found. * then make calls to methods for each file that is found.
* In a typical usage, code will subclass this so that certain methods * In a typical usage, code will subclass this so that certain methods will
* will write to different places. For instance, some code may * write to different places. For instance, some code may override the
* override the "processManifestFileLine" to write the contents to * "processManifestFileLine" to write the contents to the file system
* the file system while another may write it to a zip file. * while another may write it to a zip file.
*/ */
public abstract class ManifestReader implements ThemeDirectorConstants { public abstract class ManifestReader implements ThemeDirectorConstants {
private static final Logger s_log = /** Internal logger instance to faciliate debugging. Enable logging output
Logger.getLogger(ManifestReader.class); * by editing /WEB-INF/conf/log4j.properties int hte runtime environment
* and set com.arsdigita.themedirector.util.ManifestReader=DEBUG by
* uncommenting or adding the line. */
private static final Logger s_log = Logger.getLogger(ManifestReader.class);
private InputStream m_stream; private InputStream m_stream;
private String m_fileName; private String m_fileName;
@ -53,7 +58,9 @@ public abstract class ManifestReader implements ThemeDirectorConstants {
/** /**
* This takes in the actual input stream that is the Manifest File * This takes in the actual input stream that is the Manifest File
* so that the input stream can be correctly read * so that the input stream can be correctly read.
*
* @param stream the input stream to read
*/ */
public ManifestReader(InputStream stream) { public ManifestReader(InputStream stream) {
this(stream, null); this(stream, null);
@ -64,6 +71,8 @@ public abstract class ManifestReader implements ThemeDirectorConstants {
* so that the input stream can be correctly read. It also * so that the input stream can be correctly read. It also
* takes in the fileName so that it can be used in error messages * takes in the fileName so that it can be used in error messages
* if there is an error. * if there is an error.
* @param stream the input stream to read
* @param fileName
*/ */
public ManifestReader(InputStream stream, String fileName) { public ManifestReader(InputStream stream, String fileName) {
this(stream, fileName, null); this(stream, fileName, null);
@ -77,11 +86,11 @@ public abstract class ManifestReader implements ThemeDirectorConstants {
* @param stream The input stream to read * @param stream The input stream to read
* @param fileName The name of the file we are reading that will * @param fileName The name of the file we are reading that will
* be displayed in the case of an error. * be displayed in the case of an error.
* @param possibleServletContext The servlet context to try to use * @param possibleServletContext The servlet context to try to use when
* when looking for files listed in the Manifest. This should be * looking for files listed in the Manifest. This should be set
* set when it is know that the manifest file specifies files * when it is known that the manifest file specifies files that
* that are located under a different webapps. If the * are located under a different webapps. If the file ist not
* file is not found under this context or this context is null * found under this context or this context is null
* then the file tries to use the default ServletContext * then the file tries to use the default ServletContext
*/ */
public ManifestReader(InputStream stream, String fileName, public ManifestReader(InputStream stream, String fileName,
@ -94,13 +103,20 @@ public abstract class ManifestReader implements ThemeDirectorConstants {
/** /**
* this is the name of the file that is being parsed. This will * Retrieves the name of the file that is being parsed. Will return null
* return null if the name has not been set * if the name has not been set.
*
* @return file name if set, otherwise null
*/ */
public String getFileName() { public String getFileName() {
return m_fileName; return m_fileName;
} }
/**
* Set the name of the file that is being parsed.
*
* @param fileName
*/
public void setFileName(String fileName) { public void setFileName(String fileName) {
m_fileName = fileName; m_fileName = fileName;
} }
@ -112,8 +128,9 @@ public abstract class ManifestReader implements ThemeDirectorConstants {
* this method will only really do anything once. * this method will only really do anything once.
*/ */
public void processFile() { public void processFile() {
LineNumberReader lines =
new LineNumberReader(new InputStreamReader(m_stream)); LineNumberReader lines = new LineNumberReader(
new InputStreamReader(m_stream));
Collection extensions = ThemeDirector.getConfig() Collection extensions = ThemeDirector.getConfig()
.getDownloadFileExtensions(); .getDownloadFileExtensions();
@ -134,31 +151,28 @@ public abstract class ManifestReader implements ThemeDirectorConstants {
// extensions as specified in the config file // extensions as specified in the config file
// 4. the file starts with the default directory and is // 4. the file starts with the default directory and is
// not just the directory itself // not just the directory itself
boolean fileForDownload = fileExtensionIndex > -1 && boolean fileForDownload = fileExtensionIndex > -1
line.length() > (fileExtensionIndex+1) && && line.length() > (fileExtensionIndex+1)
extensions.contains(line.substring(fileExtensionIndex+1) && extensions.contains(line.substring(fileExtensionIndex+1)
.toLowerCase()); .toLowerCase());
// get the stream from the WAR or file system // get the stream from the WAR or file system
InputStream stream = InputStream stream = getResourceAsStream(line,
getResourceAsStream(line, m_possibleServletContext); m_possibleServletContext);
if (stream == null) { if (stream == null) {
s_log.debug s_log.debug(m_fileName + ": " + lines.getLineNumber()
(m_fileName + ": " + + ": no such resource '" + line + "'");
lines.getLineNumber() +
": no such resource '" + line + "'");
} else { } else {
processManifestFileLine(stream, line, fileForDownload); processManifestFileLine(stream, line, fileForDownload);
stream.close(); stream.close();
} }
line = lines.readLine(); line = lines.readLine();
} }
} catch (IOException e) { } catch (IOException e) {
throw new UncheckedWrapperException throw new UncheckedWrapperException("Error with " + m_fileName
("Error with " + m_fileName + ": " + + ": "
lines.getLineNumber(), e); + lines.getLineNumber(), e);
} finally { } finally {
try { try {
m_stream.close(); m_stream.close();
@ -171,33 +185,43 @@ public abstract class ManifestReader implements ThemeDirectorConstants {
/** /**
* This provides a way for child classes to look for the resource * This provides a way for child classes to look for the resource in
* in multiple places. By default, it only looks in the ServletContext * multiple places. By default, it only looks in the ServletContext.
*
* @param line
* @param possibleServletContext
* @return stream, may be null
*/ */
protected InputStream getResourceAsStream(String line, protected InputStream getResourceAsStream(String line,
String possibleServletContext) { String possibleServletContext) {
InputStream stream = null; InputStream stream = null;
if (possibleServletContext != null) { if (possibleServletContext != null) {
stream = Web.getServletContext().getContext(possibleServletContext) stream = Web.getServletContext() // gets the servlet context of
// the current thread
.getContext(possibleServletContext)
.getResourceAsStream(line); .getResourceAsStream(line);
} }
if (stream != null) { if (stream != null) {
setActualContext setActualContext(line,
(line, Web.getServletContext()
Web.getServletContext().getContext(possibleServletContext)); .getContext(possibleServletContext));
} else { } else {
stream = Web.getServletContext().getResourceAsStream(line); stream = Web.getServletContext() // servlet ctx of actual thread
.getResourceAsStream(line);
if (stream == null) { if (stream == null) {
// this means that the file is not under the passed in // this means that the file is not under the passed in
// context or the default context so let's check the "ROOT" // context nor the default context so let's check the "ROOT"
// context // context
stream = Web.getServletContext().getContext(ROOT_WEBAPP_PATH) // DEPRECATED. CCM may be installed at any context, in many
.getResourceAsStream(line); // cases dedicatedly no longer in ROOT
if (stream != null) { // stream = Web.getServletContext().getContext(ROOT_WEBAPP_PATH)
setActualContext(line, Web.getServletContext() // .getResourceAsStream(line);
.getContext(ROOT_WEBAPP_PATH)); // if (stream != null) {
} // setActualContext(line, Web.getServletContext()
// .getContext(ROOT_WEBAPP_PATH));
// }
} else { } else {
setActualContext(line, Web.getServletContext()); setActualContext(line, Web.getServletContext());
} }
@ -209,12 +233,20 @@ public abstract class ManifestReader implements ThemeDirectorConstants {
/** /**
* This provides subclasses with access to the actual ServletContext * This provides subclasses with access to the actual ServletContext
* where the line is found. The info for the line should be available * where the line is found. The info for the line should be available
* when processManifestFileLine is called for a given line * when processManifestFileLine is called for a given line.
*
* @param line
* @return
*/ */
protected ServletContext getActualContext(String line) { protected ServletContext getActualContext(String line) {
return (ServletContext)m_actualContextList.get(line); return (ServletContext)m_actualContextList.get(line);
} }
/**
*
* @param line
* @param context
*/
protected void setActualContext(String line, ServletContext context) { protected void setActualContext(String line, ServletContext context) {
m_actualContextList.put(line, context); m_actualContextList.put(line, context);
} }

View File

@ -40,8 +40,12 @@ import org.apache.log4j.Logger;
*/ */
public class ThemeDevelopmentFileManager extends ThemeFileManager { public class ThemeDevelopmentFileManager extends ThemeFileManager {
private static Logger s_log = /** Internal logger instance to faciliate debugging. Enable logging output
Logger.getLogger(ThemeDevelopmentFileManager.class); * by editing /WEB-INF/conf/log4j.properties int hte runtime environment
* and set com.arsdigita.themedirector.util.ThemeDevelopmentFileManager=DEBUG
* by uncommenting or adding the line. */
private static Logger s_log = Logger
.getLogger(ThemeDevelopmentFileManager.class);
// The code in this class borrows heavily from // The code in this class borrows heavily from
// com.arsdigita.cms.publishToFile.FileManager // com.arsdigita.cms.publishToFile.FileManager
@ -51,13 +55,16 @@ public class ThemeDevelopmentFileManager extends ThemeFileManager {
/** /**
* Constructor just delegates to super class. * Constructor just delegates to super class.
*
* @param startupDelay * @param startupDelay
* @param pollDelay * @param pollDelay
* @param baseDirectory * @param baseDirectory
*/ */
protected ThemeDevelopmentFileManager(int startupDelay, int pollDelay, protected ThemeDevelopmentFileManager(int startupDelay, int pollDelay,
String baseDirectory) { String baseDirectory) {
super(s_log, startupDelay, pollDelay, baseDirectory);
super(s_log, // Injects it's own logger
startupDelay, pollDelay, baseDirectory); // to the parent class methods!
} }

View File

@ -16,16 +16,12 @@
package com.arsdigita.themedirector.util; package com.arsdigita.themedirector.util;
import com.arsdigita.themedirector.Theme; import com.arsdigita.themedirector.Theme;
import com.arsdigita.themedirector.ThemeDirector;
import com.arsdigita.themedirector.ThemeCollection; import com.arsdigita.themedirector.ThemeCollection;
import com.arsdigita.themedirector.ThemeDirectorConstants; import com.arsdigita.themedirector.ThemeDirectorConstants;
import com.arsdigita.themedirector.ThemeFileCollection; import com.arsdigita.themedirector.ThemeFileCollection;
import com.arsdigita.persistence.SessionManager; import com.arsdigita.persistence.SessionManager;
import com.arsdigita.persistence.TransactionContext; import com.arsdigita.persistence.TransactionContext;
import com.arsdigita.themedirector.dispatcher.InternalThemePrefixerServlet; import com.arsdigita.themedirector.dispatcher.InternalThemePrefixerServlet;
import com.arsdigita.web.Application;
import com.arsdigita.web.ApplicationCollection;
import com.arsdigita.web.Web;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
@ -38,8 +34,9 @@ import org.apache.log4j.Logger;
/** /**
* Class for polling the database to look for new/updated files in * Class providing client classes (as FileManager for development and published
* the ThemeFile table. * themes) with base methods for polling the database to look for new/updated
* files in the ThemeFile table.
* *
* For "published" files, It goes through each Theme and looks at the * For "published" files, It goes through each Theme and looks at the
* last time it was published. If the last time published > last * last time it was published. If the last time published > last
@ -52,15 +49,14 @@ import org.apache.log4j.Logger;
* then it writes out the new file. If the timestamp on the file system * then it writes out the new file. If the timestamp on the file system
* is newer, it ignores the file. * is newer, it ignores the file.
* *
*
* @author <a href="mailto:randyg@redhat.com">Randy Graebner</a> * @author <a href="mailto:randyg@redhat.com">Randy Graebner</a>
*
* @version $Revision: #2 $ $DateTime: 2004/01/30 17:24:49 $ * @version $Revision: #2 $ $DateTime: 2004/01/30 17:24:49 $
*/ */
public abstract class ThemeFileManager extends Thread public abstract class ThemeFileManager extends Thread
implements ThemeDirectorConstants { implements ThemeDirectorConstants {
/** Internal logger instance to faciliate debugging */ /** Internal logger instance to faciliate debugging. Carries over the
* logger instance from the client child which actually does the work. */
private final Logger m_log; private final Logger m_log;
// The code in this class borrows heavily from // The code in this class borrows heavily from
@ -89,7 +85,9 @@ public abstract class ThemeFileManager extends Thread
* @param pollDelay * @param pollDelay
* @param baseDirectory * @param baseDirectory
*/ */
protected ThemeFileManager(Logger log, int startupDelay, int pollDelay, protected ThemeFileManager(Logger log,
int startupDelay,
int pollDelay,
String baseDirectory) { String baseDirectory) {
m_log = log; m_log = log;
m_startupDelay = startupDelay; m_startupDelay = startupDelay;
@ -122,6 +120,7 @@ public abstract class ThemeFileManager extends Thread
* Watch file for entries to process. The main routine that starts * Watch file for entries to process. The main routine that starts
* file processing. * file processing.
*/ */
@Override
public void run() { public void run() {
m_log.info("Start polling file in " + m_startupDelay + "s."); m_log.info("Start polling file in " + m_startupDelay + "s.");
if (m_lastRunDate == null) { if (m_lastRunDate == null) {
@ -186,6 +185,8 @@ public abstract class ThemeFileManager extends Thread
* This allows an outside piece of code to force an automatic update * This allows an outside piece of code to force an automatic update
* on a single theme instead of making it wait for the thread to wake * on a single theme instead of making it wait for the thread to wake
* up. * up.
*
* @param theme
*/ */
public void updateThemeNow(Theme theme) { public void updateThemeNow(Theme theme) {
updateTheme(theme); updateTheme(theme);
@ -194,13 +195,15 @@ public abstract class ThemeFileManager extends Thread
/** /**
* This returns the base directory to use when writing out files * This returns the base directory to use when writing out files.
* THIS IS A HACK BECAUSE IT REQUIRES A SERVER TO BE RUNNING * THIS IS A HACK BECAUSE IT REQUIRES A SERVER TO BE RUNNING.
*
* @return
*/ */
protected String getBaseDirectory() { protected String getBaseDirectory() {
if (m_baseDirectory == null) { if (m_baseDirectory == null) {
// Because the constructor sets the base deirectory this should // Because the constructor sets the base directory this should
// never happen, but just in case .... // never happen, but just in case ....
// ThemeDirector may execute in a different web application context // ThemeDirector may execute in a different web application context
// as core oder CMS. To determine the actual context we may ask // as core oder CMS. To determine the actual context we may ask
@ -231,13 +234,18 @@ public abstract class ThemeFileManager extends Thread
} }
/** /**
* this typically returns something like "getBaseDirectory() + PUB_DIR" * This typically returns something like "getBaseDirectory() + PUB_DIR".
*
* @return
*/ */
protected abstract String getManagerSpecificDirectory(); protected abstract String getManagerSpecificDirectory();
/** /**
* This allows subclasses to filter the collection as appropriate * This allows subclasses to filter the collection as appropriate.
* (e.g. only return "live" files or only "draft" files). * (e.g. only return "live" files or only "draft" files).
*
* @param theme
* @return
*/ */
protected abstract ThemeFileCollection getThemeFilesCollection(Theme theme); protected abstract ThemeFileCollection getThemeFilesCollection(Theme theme);
@ -271,6 +279,8 @@ public abstract class ThemeFileManager extends Thread
* This looks at all of the files in the db for the passed in theme * This looks at all of the files in the db for the passed in theme
* and makes sure that this servers file system has all of the * and makes sure that this servers file system has all of the
* updated files. * updated files.
*
* @param theme
*/ */
protected void updateTheme(Theme theme) { protected void updateTheme(Theme theme) {
String stub = getManagerSpecificDirectory(); String stub = getManagerSpecificDirectory();
@ -329,7 +339,10 @@ public abstract class ThemeFileManager extends Thread
/*** /***
* Sleep for n seconds * Sleep for n seconds.
*
* @param n
* @return
***/ ***/
protected boolean sleepSeconds(long n) { protected boolean sleepSeconds(long n) {
try { try {