/*
* Copyright (C) 2009 eXo Platform SAS.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.exoplatform.container;
import junit.framework.AssertionFailedError;
import org.exoplatform.commons.utils.PropertyManager;
import org.exoplatform.commons.utils.SecurityHelper;
import org.exoplatform.commons.utils.Tools;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URL;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Set;
/**
* <p>An helper for building a root container and a portal container. I have done several attempt to make easily
* and safe root/portal container boot for unit test. This one is my best attempt so far.</p>
*
* <p>Note that the portal container are booted in the order they are declared first.</p>
*
* @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
* @version $Revision$
*/
public class ContainerBuilder
{
/** A hack used during the boot of a portal container. */
private final ThreadLocal<String> bootedPortalName = new ThreadLocal<String>();
/** . */
private ClassLoader loader;
/** . */
private List<URL> configURLs;
/** . */
private LinkedHashMap<String, List<URL>> portalConfigURLs;
/** . */
private Set<String> profiles;
public ContainerBuilder()
{
this.loader = Thread.currentThread().getContextClassLoader();
this.configURLs = new ArrayList<URL>();
this.portalConfigURLs = new LinkedHashMap<String, List<URL>>();
}
public ContainerBuilder withRoot(String configPath)
{
configURLs.addAll(urls(configPath));
return this;
}
public ContainerBuilder withRoot(URL configURL)
{
configURLs.add(configURL);
return this;
}
public ContainerBuilder withPortal(String configPath)
{
return withPortal("portal", configPath);
}
public ContainerBuilder withPortal(String portalName, String configPath)
{
for (URL configURL : urls(configPath))
{
withPortal(portalName, configURL);
}
return this;
}
public ContainerBuilder withPortal(URL configURL)
{
return withPortal("portal", configURL);
}
public ContainerBuilder withPortal(String portalName, URL configURL)
{
List<URL> urls = portalConfigURLs.get(portalName);
if (urls == null)
{
urls = new ArrayList<URL>();
portalConfigURLs.put(portalName, urls);
}
urls.add(configURL);
return this;
}
public ContainerBuilder withLoader(ClassLoader loader)
{
this.loader = loader;
return this;
}
public ContainerBuilder profiledBy(String... profiles)
{
this.profiles = Tools.set(profiles);
return this;
}
private List<URL> urls(String path)
{
try
{
return Collections.list(loader.getResources(path));
}
catch (IOException e)
{
AssertionFailedError err = new AssertionFailedError();
err.initCause(e);
throw err;
}
}
public RootContainer build()
{
PrivilegedExceptionAction<RootContainer> action = new PrivilegedExceptionAction<RootContainer>()
{
public RootContainer run() throws Exception
{
return _build();
}
};
try
{
return SecurityHelper.doPrivilegedExceptionAction(action);
}
catch (PrivilegedActionException pae)
{
Throwable cause = pae.getCause();
AssertionFailedError err = new AssertionFailedError();
err.initCause(cause);
throw err;
}
}
private RootContainer _build() throws Exception
{
//
if (configURLs.size() == 0)
{
throw new IllegalStateException("Must provide at least one URL for building the root container");
}
// Must clear the top container first otherwise it's not going to work well
// it's a big ugly but I don't want to change anything in the ExoContainerContext class for now
// and this is for unit testing
Field topContainerField = ExoContainerContext.class.getDeclaredField("topContainer");
topContainerField.setAccessible(true);
topContainerField.set(null, null);
ExoContainerContext.setCurrentContainer(null);
// Same remark than above
Field singletonField = RootContainer.class.getDeclaredField("singleton_");
singletonField.setAccessible(true);
singletonField.set(null, null);
// Setup profiles
if (profiles == null)
{
PropertyManager.setProperty(PropertyManager.RUNTIME_PROFILES, "");
}
else
{
StringBuilder builder = new StringBuilder();
for (Iterator<String> i = profiles.iterator();i.hasNext();)
{
builder.append(i.next());
if (i.hasNext())
{
builder.append(',');
}
}
PropertyManager.setProperty(PropertyManager.RUNTIME_PROFILES, builder.toString());
}
//
ClassLoader rootCL = new ClassLoader(loader)
{
@Override
public Enumeration<URL> getResources(String name) throws IOException
{
if ("conf/configuration.xml".equals(name))
{
return Collections.enumeration(configURLs);
}
else if ("conf/portal/configuration.xml".equals(name))
{
String portalName = bootedPortalName.get();
return Collections.enumeration(portalConfigURLs.get(portalName));
}
else if ("conf/portal/test-configuration.xml".equals(name))
{
return Collections.enumeration(Collections.<URL>emptyList());
}
else
{
return super.getResources(name);
}
}
};
//
ClassLoader oldCL = Thread.currentThread().getContextClassLoader();
// Boot root container
RootContainer root;
try
{
Thread.currentThread().setContextClassLoader(rootCL);
//
root = RootContainer.getInstance();
PortalContainer.reloadConfig();
//
for (String portalName : portalConfigURLs.keySet())
{
try
{
bootedPortalName.set(portalName);
root.getPortalContainer(portalName);
}
finally
{
bootedPortalName.set(null);
}
}
}
finally
{
Thread.currentThread().setContextClassLoader(oldCL);
}
//
return root;
}
public static RootContainer bootstrap(URL configurationURL, String... profiles)
{
ContainerBuilder builder = new ContainerBuilder();
builder.withRoot(configurationURL);
builder.profiledBy(profiles);
return builder.build();
}
}