/* ====================================================================
* 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.modules.actions.controllers;
// Jetspeed stuff
import org.apache.jetspeed.om.registry.PortletEntry;
import org.apache.jetspeed.om.profile.IdentityElement;
import org.apache.jetspeed.om.profile.Entry;
import org.apache.jetspeed.om.profile.psml.PsmlControl;
import org.apache.jetspeed.om.profile.MetaInfo;
import org.apache.jetspeed.om.profile.Layout;
import org.apache.jetspeed.om.profile.Parameter;
import org.apache.jetspeed.om.profile.Portlets;
import org.apache.jetspeed.om.profile.Reference;
import org.apache.jetspeed.om.profile.psml.PsmlParameter;
import org.apache.jetspeed.om.profile.psml.PsmlLayout;
import org.apache.jetspeed.portal.Portlet;
import org.apache.jetspeed.portal.PortletSet;
import org.apache.jetspeed.portal.PortletController;
import org.apache.jetspeed.services.rundata.JetspeedRunData;
import org.apache.jetspeed.services.Registry;
import org.apache.jetspeed.services.statemanager.SessionState;
// Turbine stuff
import org.apache.turbine.modules.ActionLoader;
import org.apache.turbine.services.localization.Localization;
import org.apache.turbine.util.Log;
import org.apache.turbine.util.RunData;
// Velocity Stuff
import org.apache.velocity.context.Context;
// Java stuff
import java.util.ArrayList;
import java.util.Collections;
import java.util.Vector;
import java.util.List;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Map;
import java.util.HashMap;
import java.util.StringTokenizer;
/**
* This action builds a context suitable for controllers handling
* grid positioned layout using PortletSet.Constraints
*
* @author <a href="mailto:raphael@apache.org">Rapha�l Luta</a>
* @author <a href="mailto:paulsp@apache.org">Paul Spencer</a>
* @version $Id: MultiColumnControllerAction.java,v 1.25 2002/11/17 03:06:27 paulsp Exp $
*/
public class MultiColumnControllerAction extends VelocityControllerAction
{
/**
* Subclasses must override this method to provide default behavior
* for the portlet action
*/
protected void buildNormalContext(PortletController controller,
Context context,
RunData rundata)
{
try
{
// retrieve the number of columns
String cols = controller.getConfig().getInitParameter("cols");
int colNum = 0;
int rowNum = 0;
try
{
colNum = Integer.parseInt(cols);
}
catch (Exception e)
{
// not an integer or null, default to standarrd value
colNum = 3;
}
context.put("colNum", String.valueOf(colNum));
//retrieve the size for each of the columns
String sizes = controller.getConfig().getInitParameter("sizes");
context.put("sizes", getCellSizes(sizes));
//retrieve the class for each of the columns
String columnClasses = controller.getConfig().getInitParameter("col_classes");
context.put("col_classes", getCellClasses(columnClasses));
PortletSet set = controller.getPortlets();
// normalize the constraints and calculate max num of rows needed
Enumeration en = set.getPortlets();
int row = 0;
int col = 0;
while (en.hasMoreElements())
{
Portlet p = (Portlet) en.nextElement();
PortletSet.Constraints
constraints = p.getPortletConfig().getConstraints();
if ((constraints != null)
&& (constraints.getColumn() != null)
&& (constraints.getRow() != null))
{
col = constraints.getColumn().intValue();
if (col > colNum)
{
constraints.setColumn(new Integer(col % colNum));
}
row = constraints.getRow().intValue();
if (row > rowNum)
{
rowNum = row;
}
}
}
row = (int) Math.ceil(set.size() / colNum);
if (row > rowNum)
{
rowNum = row;
}
Log.debug("Controller calculated setSize " + set.size() + " row " + row + " colNum: " + colNum + " rowNum: " + rowNum);
// initialize the result position table and the work list
List[] table = new List[colNum];
List filler = Collections.nCopies(rowNum + 1, null);
for (int i = 0; i < colNum; i++)
{
table[i] = new ArrayList();
table[i].addAll(filler);
}
List work = new ArrayList();
//position the constrained elements and keep a reference to the
//others
for (int i = 0; i < set.size(); i++)
{
Portlet p = set.getPortletAt(i);
PortletSet.Constraints
constraints = p.getPortletConfig().getConstraints();
if ((constraints != null)
&& (constraints.getColumn() != null)
&& (constraints.getRow() != null)
&& (constraints.getColumn().intValue() < colNum))
{
row = constraints.getRow().intValue();
col = constraints.getColumn().intValue();
table[col].set(row, p);
}
else
{
work.add(p);
}
}
//insert the unconstrained elements in the table
Iterator i = work.iterator();
for (row = 0; row < rowNum; row++)
{
for (col = 0; i.hasNext() && (col < colNum); col++)
{
if (table[col].get(row) == null)
{
table[col].set(row, i.next());
}
}
}
// now cleanup any remaining null elements
for (int j = 0; j < table.length; j++)
{
i = table[j].iterator();
while (i.hasNext())
{
Object obj = i.next();
if (obj == null)
{
i.remove();
}
}
}
if (Log.getLogger().isDebugEnabled())
{
dumpColumns(table);
}
context.put("portlets", table);
}
catch (Exception e)
{
Log.error(e);
}
}
/**
* Subclasses must override this method to provide default behavior
* for the portlet action
*/
protected void buildCustomizeContext(PortletController controller,
Context context,
RunData rundata)
{
JetspeedRunData jdata = (JetspeedRunData) rundata;
// get the customization state for this page
SessionState customizationState = jdata.getPageSessionState();
super.buildCustomizeContext(controller, context, rundata);
List[] columns = null;
// retrieve the number of columns
String cols = controller.getConfig().getInitParameter("cols");
int colNum = 0;
try
{
colNum = Integer.parseInt(cols);
}
catch (Exception e)
{
// not an integer or null, default to standarrd value
colNum = 3;
}
context.put("colNum", String.valueOf(colNum));
//retrieve the size for each of the columns
String sizes = controller.getConfig().getInitParameter("sizes");
context.put("sizes", getCellSizes(sizes));
//retrieve the class for each of the columns
String columnClasses = controller.getConfig().getInitParameter("col_classes");
context.put("col_classes", getCellClasses(columnClasses));
columns = (List[]) customizationState.getAttribute("customize-columns");
PortletSet customizedSet = (PortletSet) jdata.getCustomized();
Portlets set = jdata.getCustomizedProfile()
.getDocument()
.getPortletsById(customizedSet.getID());
Log.debug("MultiCol: columns " + columns + " set " + set);
if ((columns != null) && (columns.length == colNum))
{
int eCount = 0;
for (int i = 0; i < columns.length; i++)
{
eCount += columns[i].size();
}
Log.debug("MultiCol: eCount " + eCount + " setCount" + set.getEntryCount() + set.getPortletsCount());
if (eCount != set.getEntryCount() + set.getPortletsCount())
{
Log.debug("MultiCol: rebuilding columns ");
columns = buildColumns(set, colNum);
}
}
else
{
Log.debug("MultiCol: rebuilding columns ");
columns = buildColumns(set, colNum);
}
customizationState.setAttribute("customize-columns", columns);
context.put("portlets", columns);
Map titles = new HashMap();
for (int col = 0; col < columns.length; col++)
{
for (int row = 0; row < columns[col].size(); row++)
{
IdentityElement identityElement = (IdentityElement) columns[col].get(row);
MetaInfo metaInfo = identityElement.getMetaInfo();
if ((metaInfo != null) && (metaInfo.getTitle() != null))
{
titles.put(identityElement.getId(), metaInfo.getTitle());
continue;
}
if (identityElement instanceof Entry)
{
Entry entry = (Entry) identityElement;
PortletEntry pentry = (PortletEntry) Registry.getEntry(Registry.PORTLET, entry.getParent());
if ((pentry != null) && (pentry.getTitle() != null))
{
titles.put(entry.getId(), pentry.getTitle());
continue;
}
titles.put(entry.getId(), entry.getParent());
continue;
}
if (identityElement instanceof Reference)
{
titles.put(identityElement.getId(), Localization.getString("CUSTOMIZER_REF_DEFAULTTITLE"));
continue;
}
// Let's make sure their is a title
titles.put(identityElement.getId(), Localization.getString("CUSTOMIZER_NOTITLESET"));
}
}
context.put("titles", titles);
context.put("action", "controllers.MultiColumnControllerAction");
}
/**
* Cancel the current customizations. If this was the last customization
* on the stack, then return the user to the home page.
*/
public void doCancel(RunData data, Context context)
{
// move one level back in customization
((JetspeedRunData) data).setCustomized(null);
// if we are all done customization
if (((JetspeedRunData) data).getCustomized() == null)
{
try
{
ActionLoader.getInstance().exec(data, "controls.EndCustomize");
}
catch (Exception e)
{
Log.error("Unable to load action controls.EndCustomize ", e);
}
}
}
public void doSave(RunData data, Context context)
{
// get the customization state for this page
SessionState customizationState = ((JetspeedRunData) data).getPageSessionState();
// update the changes made here to the profile being edited
List[] columns = (List[]) customizationState.getAttribute("customize-columns");
for (int col = 0; col < columns.length; col++)
{
for (int row = 0; row < columns[col].size(); row++)
{
setPosition((IdentityElement) columns[col].get(row), col, row);
}
}
// move one level back in customization
((JetspeedRunData) data).setCustomized(null);
// if we are all done customization
if (((JetspeedRunData) data).getCustomized() == null)
{
// save the edit profile and make it current
try
{
((JetspeedRunData) data).getCustomizedProfile().store();
}
catch (Exception e)
{
Log.error("Unable to save profile ", e);
}
try
{
ActionLoader.getInstance().exec(data, "controls.EndCustomize");
}
catch (Exception e)
{
Log.error("Unable to load action controls.EndCustomize ", e);
}
}
}
public void doDelete(RunData data, Context context)
{
JetspeedRunData jdata = (JetspeedRunData) data;
// get the customization state for this page
SessionState customizationState = jdata.getPageSessionState();
PortletSet customizedSet = (PortletSet) jdata.getCustomized();
int col = data.getParameters().getInt("col", -1);
int row = data.getParameters().getInt("row", -1);
List[] columns = (List[]) customizationState.getAttribute("customize-columns");
if (columns == null)
{
return;
}
if ((col > -1) && (row > -1))
{
try
{
IdentityElement identityElement = (IdentityElement) columns[col].get(row);
columns[col].remove(row);
Portlets portlets = jdata.getCustomizedProfile()
.getDocument()
.getPortletsById(customizedSet.getID());
if (portlets != null)
{
if (identityElement instanceof Entry)
{
for (int i = 0; i < portlets.getEntryCount(); i++)
{
if (portlets.getEntry(i) == identityElement)
{
portlets.removeEntry(i);
}
}
}
else if (identityElement instanceof Reference)
{
for (int i = 0; i < portlets.getReferenceCount(); i++)
{
if (portlets.getReference(i) == identityElement)
{
portlets.removeReference(i);
}
}
}
}
}
catch (Exception e)
{
// probably got wrong coordinates
}
}
}
public void doLeft(RunData data, Context context)
{
// get the customization state for this page
SessionState customizationState = ((JetspeedRunData) data).getPageSessionState();
List[] columns = (List[]) customizationState.getAttribute("customize-columns");
int col = data.getParameters().getInt("col", -1);
int row = data.getParameters().getInt("row", -1);
if (columns == null)
{
return;
}
if ((col > 0) && (row > -1))
{
move(columns, col, row, col - 1, row);
}
}
public void doRight(RunData data, Context context)
{
// get the customization state for this page
SessionState customizationState = ((JetspeedRunData) data).getPageSessionState();
List[] columns = (List[]) customizationState.getAttribute("customize-columns");
int col = data.getParameters().getInt("col", -1);
int row = data.getParameters().getInt("row", -1);
if (columns == null)
{
return;
}
if ((col > -1) && (row > -1) && (col < columns.length - 1))
{
move(columns, col, row, col + 1, row);
}
}
public void doUp(RunData data, Context context)
{
// get the customization state for this page
SessionState customizationState = ((JetspeedRunData) data).getPageSessionState();
List[] columns = (List[]) customizationState.getAttribute("customize-columns");
int col = data.getParameters().getInt("col", -1);
int row = data.getParameters().getInt("row", -1);
if (columns == null)
{
return;
}
if ((col > -1) && (row > 0))
{
move(columns, col, row, col, row - 1);
}
}
public void doDown(RunData data, Context context)
{
// get the customization state for this page
SessionState customizationState = ((JetspeedRunData) data).getPageSessionState();
List[] columns = (List[]) customizationState.getAttribute("customize-columns");
int col = data.getParameters().getInt("col", -1);
int row = data.getParameters().getInt("row", -1);
if (columns == null)
{
return;
}
if ((col > -1) && (row > -1) && (row < columns[col].size() - 1))
{
move(columns, col, row, col, row + 1);
}
}
public void doControl(RunData data, Context context)
{
JetspeedRunData jdata = (JetspeedRunData) data;
String controlName = data.getParameters().getString("control");
String id = data.getParameters().getString("js_peid");
try
{
Entry entry = jdata.getCustomizedProfile().getDocument().getEntryById(id);
if (entry != null)
{
if (controlName != null && controlName.trim().length() > 0)
{
PsmlControl control = new PsmlControl();
control.setName(controlName);
if (control != null)
{
entry.setControl(control);
}
}
else
{
entry.setControl(null);
}
}
}
catch (Exception e)
{
Log.error(e);
}
}
protected static void setPosition(IdentityElement identityElement, int col, int row)
{
boolean colFound = false;
boolean rowFound = false;
if (identityElement != null)
{
Layout layout = identityElement.getLayout();
if (layout == null)
{
layout = new PsmlLayout();
identityElement.setLayout(layout);
}
for (int i = 0; i < layout.getParameterCount(); i++)
{
Parameter p = layout.getParameter(i);
if (p.getName().equals("column"))
{
p.setValue(String.valueOf(col));
colFound = true;
}
else if (p.getName().equals("row"))
{
p.setValue(String.valueOf(row));
rowFound = true;
}
}
if (!colFound)
{
Parameter p = new PsmlParameter();
p.setName("column");
p.setValue(String.valueOf(col));
layout.addParameter(p);
}
if (!rowFound)
{
Parameter p = new PsmlParameter();
p.setName("row");
p.setValue(String.valueOf(row));
layout.addParameter(p);
}
}
}
protected static void move(List[] cols, int oCol, int oRow, int nCol, int nRow)
{
Object obj = null;
if ((oCol < cols.length) && (oRow < cols[oCol].size()))
{
obj = cols[oCol].get(oRow);
if (obj != null)
{
cols[oCol].remove(oRow);
}
}
if (obj != null)
{
if (nCol < cols.length)
{
if (nRow < cols[nCol].size())
{
cols[nCol].add(nRow, obj);
}
else
{
cols[nCol].add(obj);
}
}
}
}
protected static List[] buildColumns(Portlets set, int colNum)
{
// normalize the constraints and calculate max num of rows needed
Iterator iterator = set.getEntriesIterator();
int row = 0;
int col = 0;
int rowNum = 0;
while (iterator.hasNext())
{
IdentityElement identityElement = (IdentityElement) iterator.next();
Layout layout = identityElement.getLayout();
if (layout != null)
{
for (int p = 0; p < layout.getParameterCount(); p++)
{
Parameter prop = layout.getParameter(p);
try
{
if (prop.getName().equals("row"))
{
row = Integer.parseInt(prop.getValue());
if (row > rowNum)
{
rowNum = row;
}
}
else if (prop.getName().equals("column"))
{
col = Integer.parseInt(prop.getValue());
if (col > colNum)
{
prop.setValue(String.valueOf(col % colNum));
}
}
}
catch (Exception e)
{
//ignore any malformed layout properties
}
}
}
}
int sCount = set.getEntryCount() + set.getPortletsCount();
row = (sCount / colNum) + 1;
if (row > rowNum)
{
rowNum = row;
}
Log.debug("Controller customize colNum: " + colNum + " rowNum: " + rowNum);
// initialize the result position table and the work list
List[] table = new List[colNum];
List filler = Collections.nCopies(rowNum + 1, null);
for (int i = 0; i < colNum; i++)
{
table[i] = new ArrayList();
table[i].addAll(filler);
}
List work = new ArrayList();
//position the constrained elements and keep a reference to the
//others
for (int i = 0; i < set.getEntryCount(); i++)
{
addElement(set.getEntry(i), table, work, colNum);
}
// Add references
for (int i = 0; i < set.getReferenceCount(); i++)
{
addElement(set.getReference(i), table, work, colNum);
}
//insert the unconstrained elements in the table
Iterator i = work.iterator();
for (row = 0; row < rowNum; row++)
{
for (col = 0; i.hasNext() && (col < colNum); col++)
{
if (table[col].get(row) == null)
{
Log.debug("Set portlet at col " + col + " row " + row);
table[col].set(row, i.next());
}
}
}
// now cleanup any remaining null elements
for (int j = 0; j < table.length; j++)
{
Log.debug("Column " + j);
i = table[j].iterator();
while (i.hasNext())
{
Object obj = i.next();
Log.debug("Element " + obj);
if (obj == null)
{
i.remove();
}
}
}
return table;
}
/** Parses the size config info and returns a list of
* size values for the current set
*
* @param sizeList java.lang.String a comma separated string a values
* @return a List of values
*/
protected static List getCellSizes(String sizeList)
{
List list = new Vector();
if (sizeList != null)
{
StringTokenizer st = new StringTokenizer(sizeList, ",");
while (st.hasMoreTokens())
{
list.add(st.nextToken());
}
}
return list;
}
protected static List getCellClasses(String classlist)
{
List list = new Vector();
if (classlist != null)
{
StringTokenizer st = new StringTokenizer(classlist, ",");
while (st.hasMoreTokens())
{
list.add(st.nextToken());
}
}
return list;
}
/**
* Add an element to the "table" or "work" objects. If the element is
* unconstrained, and the position is within the number of columns, then
* the element is added to "table". Othewise the element is added to "work"
*
* @param element to add
* @param table of positioned elements
* @param work list of un-positioned elements
* @param columnCount Number of colum
*/
protected static void addElement(IdentityElement element, List[] table, List work, int columnCount)
{
Layout layout = element.getLayout();
int row = -1;
int col = -1;
if (layout != null)
{
try
{
for (int p = 0; p < layout.getParameterCount(); p++)
{
Parameter prop = layout.getParameter(p);
if (prop.getName().equals("row"))
{
row = Integer.parseInt(prop.getValue());
}
else if (prop.getName().equals("column"))
{
col = Integer.parseInt(prop.getValue());
}
}
}
catch (Exception e)
{
//ignore any malformed layout properties
}
}
Log.debug("Constraints col " + col + " row " + row);
if ((row >= 0) && (col >= 0) && (col < columnCount))
{
table[col].set(row, element);
}
else
{
if (layout != null)
{
// We got here because the column, as defined in the layout,
// is greater then the numner of columns. This usually
// happens when the number of column has been decreased.
// Delete the offending layout. It may be recreated with
// the correct values.
element.setLayout(null);
layout = null;
}
work.add(element);
}
}
protected void dumpColumns(List[] cols)
{
for (int i = 0; i < cols.length; i++)
{
Log.debug("Column " + i);
for (int j = 0; j < cols[i].size(); j++)
{
Log.debug("Row " + j + " object: " + cols[i].get(j));
}
}
}
}