View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.jetspeed.layout.impl;
18  
19  import java.util.HashMap;
20  import java.util.Iterator;
21  import java.util.List;
22  import java.util.Set;
23  import java.util.Vector;
24  import java.util.Map;
25  
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  import org.apache.jetspeed.layout.Coordinate;
29  import org.apache.jetspeed.layout.PortletPlacementException;
30  import org.apache.jetspeed.layout.PortletPlacementContext;
31  import org.apache.jetspeed.om.page.Fragment;
32  import org.apache.jetspeed.om.page.Page;
33  import org.apache.jetspeed.request.RequestContext;
34  
35  
36  /***
37   * Portal Placement Context
38   * 
39   * The purpose of the object is to provide an API that
40   * can be used to move a portlet fragment on the page.
41   * This includes moving, adding, removing and getting
42   * information about portlets that are on the page and
43   * portlets that are available to be added to the page.
44   * 
45   * An important note about this object:
46   * This object is really only intended to be used to do
47   * a single operation such as "moveabs" or "add".  After
48   * performing the operation, the hashmap data structures
49   * are not correct and should not be used for subsequent
50   * operations.  The reason they are incorrect is that when
51   * a fragment is moved, the coordinate of fragments below
52   * it are now different.  These could be updated, but it
53   * really doesn't serve a purpose since this is a short
54   * lived object.
55   * 
56   * @author <a>David Gurney</a>
57   * @author <a href="mailto:taylor@apache.org">David Sean Taylor</a>
58   * @version $Id: $
59   */
60  public class PortletPlacementContextImpl implements PortletPlacementContext 
61  {
62      private static final int NO_DEPTH_LIMIT = -1;
63  
64      /*** Logger */
65      private Log log = LogFactory.getLog(PortletPlacementContextImpl.class);
66  
67  	// Columns are reference by index, the rows are held
68  	// in the array list as shown below:
69  	//
70  	// [0]        [1]          [2]
71  	// ArrayList  ArrayList    ArrayList
72  	//  Row1Frag   Row1Frag     Row1Frag
73  	//  Row2Frag   Row2Frag     Row2Frag
74  	//  Row3Frag   Row3Frag     Row3Frag
75  	//  ...
76  	//
77  	protected Vector[] columnsList = null;
78  	
79  	// Used as a convience when looking up a particular fragment
80  	//
81  	// Key is Fragment, value is a Coordinate object
82  	protected Map fragmentCoordinateMap = new HashMap();
83  	
84  	// Used as a convience when looking up a particular fragment by id
85  	//
86  	// Key is the Fragment id (String), value is the Fragment
87  	protected Map fragmentMap = new HashMap();
88  	
89  	// Number of columns found
90  	protected int numberOfColumns = -1;
91  	
92      protected Page page;
93      protected Fragment containerFragment;
94          
95  	public PortletPlacementContextImpl(RequestContext requestContext) 
96      throws PortletPlacementException 
97      {
98  		init(requestContext, null, NO_DEPTH_LIMIT);
99  	}
100     
101     public PortletPlacementContextImpl(RequestContext requestContext, Fragment container, int maxdepth) 
102     throws PortletPlacementException 
103     {
104         init(requestContext, container, maxdepth);
105     }
106 	
107 	// Initialize the data structures by getting the fragments
108 	// from the page manager
109 	protected void init(RequestContext requestContext, Fragment container, int maxdepth) 
110     throws PortletPlacementException 
111     {
112         this.page = requestContext.getPage();
113         if ( container == null )
114         {
115             container = page.getRootFragment();
116         }
117         this.containerFragment = container;
118         
119         // Recursively process each fragment
120         processFragment( container, maxdepth, 0 );
121 
122         // The final step is to populate the array with the fragments
123 		populateArray();
124         
125         //debugFragments( "init" );
126 	}
127 	
128 	/***
129 	 * Evaluate each portlet fragment and populate the internal data
130 	 * structures
131 	 */
132 	protected int processFragment( Fragment fragment, int remainingDepth, int rowCount ) 
133     throws PortletPlacementException 
134     {
135         // Process this fragment, then its children
136 		if(fragment != null) 
137         {
138 			// Only process portlet fragments
139 			//if(fragment.getType().equalsIgnoreCase("portlet")) 
140             {
141 				// Get the column and row of this fragment
142 				int col = getFragmentCol(fragment);
143 				int row = getFragmentRow(fragment);
144 		        
145 		        if(row < 0) 
146                 {
147                     row = rowCount;
148                 }
149 	        	// Add this fragment to the data structures
150 	        	addFragmentInternal(fragment, col, row);
151                 rowCount++;
152 			}
153 			
154             if ( remainingDepth == NO_DEPTH_LIMIT || remainingDepth > 0 )
155             {
156                 // Process the children
157                 List children = fragment.getFragments();
158                 int childRowCount = 0;
159                 for(int ix = 0; ix < children.size(); ix++) 
160                 {
161                     Fragment childFrag = (Fragment)children.get(ix);
162 				
163                     if(childFrag != null) 
164                     {
165                         childRowCount = processFragment(childFrag, ((remainingDepth == NO_DEPTH_LIMIT) ? NO_DEPTH_LIMIT : remainingDepth-1), childRowCount );
166                     }
167                 }
168 			}
169 		}
170         return rowCount;
171 	}
172     
173     public Fragment debugFragments(String debug)
174     {       
175         StringBuffer out = new StringBuffer();
176         out.append( "PortletPlacementContext - " ).append( debug ).append( " - container: " ).append( containerFragment == null ? "<null>" : ( containerFragment.getId() + " / " + containerFragment.getType() ) ).append( "\n" );
177         for (int ix = 0; ix < this.columnsList.length; ix++)
178         {
179             Vector column = this.columnsList[ix];
180             out.append( "   column " ).append( ix ).append( "\n" );
181             Iterator frags = column.iterator();
182             while ( frags.hasNext() )
183             {
184                 Fragment f = (Fragment)frags.next();
185                 out.append( "      frag " ).append( f == null ? "<null>" : f.getId() );
186                 if ( f != null )
187                     out.append( " / " ).append( f.getType() ).append( " col=" ).append( f.getLayoutColumn() ).append( " row=" ).append( f.getLayoutRow() );
188                 out.append( "\n" );
189             }
190         }
191         log.debug( out.toString() );
192         return containerFragment;
193     }
194 
195     /***
196      * Takes the internal portlet placement state and stores back
197      * out to fragment state
198      * 
199      * @return the managed page layout with updated fragment state. 
200      */
201     public Page syncPageFragments()
202     {        
203         for (int col = 0; col < this.columnsList.length; col++)
204         {
205             Vector column = this.columnsList[col];
206             Iterator frags = column.iterator();
207             int row = 0;
208             while (frags.hasNext())
209             {
210                 Fragment f = (Fragment)frags.next();
211                 if (f == null)
212                     continue;
213                 f.setLayoutColumn(col);
214                 f.setLayoutRow(row);
215                 row++;
216             }
217         }
218         //debugFragments( "syncPageFragments" );
219         return page;
220     }
221     
222 	// Helper method
223 	// The implementation will probably change to get this information
224 	// directly from the fragment via fragment.getFragmentCol()
225 	protected int getFragmentRow(Fragment fragment)
226     {
227         return fragment.getLayoutRow();
228 	}
229 	
230 	// The implementation will probably change to get this information
231 	// directly from the fragment via fragment.getFragmentRow()
232 	protected int getFragmentCol(Fragment fragment) 
233     {
234         int col = fragment.getLayoutColumn();
235         if (col < 0)
236             col = 0;
237         return col;
238 	}
239 	
240 	// Adds the fragment to the internal data structures
241 	protected void addFragmentInternal(Fragment fragment, int col, int row) 
242     {
243 		// Create a Coordinate object to hold the row and column
244 		CoordinateImpl coordinate = new CoordinateImpl(col, row);
245 		
246 		// Save the fragment in the lookup hash
247 		this.fragmentCoordinateMap.put(fragment, coordinate);
248 		this.fragmentMap.put(fragment.getId(), fragment);
249 		
250 		// Establish the maximum column number
251 		if(col > this.numberOfColumns) 
252         {
253 			this.numberOfColumns = col + 1;
254 		}
255 	}
256 	
257 	/***
258 	 * Now that we know the number of columns, the array can be
259 	 * constructed and populated
260 	 */
261 	protected void populateArray() throws PortletPlacementException 
262     {
263 		if(this.numberOfColumns == -1) 
264         {
265 			//throw new PortletPlacementException("no columns found");
266             this.numberOfColumns = 1; // create a new column
267 		}
268 		
269 		// Allocate the memory for the array of ArrayLists
270 		// Add one since it is zero based
271 		this.columnsList = new Vector[this.numberOfColumns + 1];
272 		
273 		// Put an array list into each index
274 		for(int i = 0; i < this.numberOfColumns + 1; i++) 
275         {
276 			this.columnsList[i] = new Vector();
277 		}
278 		
279 		// Get all of the fragments from the hashmap
280 		Set keys = this.fragmentCoordinateMap.keySet();
281 		Iterator keyIterator = keys.iterator();
282 		while(keyIterator.hasNext()) 
283         {
284 			// The key is a Fragment
285 			Fragment fragment = (Fragment) keyIterator.next();
286 			
287 			// Get the Coordinate associated with this fragment
288 			Coordinate coordinate = (Coordinate)this.fragmentCoordinateMap.get(fragment);
289 			
290 			// Make sure we have both
291 			if(fragment != null && coordinate != null) 
292             {
293 				// Get the array list for the column
294 				Vector columnArray = this.columnsList[coordinate.getOldCol()];
295 				
296 				int row = coordinate.getOldRow();
297 				
298 				// Before setting the fragment in the array it might
299 				// be necessary to add blank rows before this row
300 				// An ArrayList can only set an element that already exists
301 				prepareList(columnArray, row);
302 				
303 				// Place the fragment in the array list using the row
304 				columnArray.set(row, fragment);
305 			}
306 		}
307 	}
308 	
309 	// Ensures that the array list has at least row number of rows
310 	protected void prepareList(Vector list, int row) 
311     {
312 		if(list != null) 
313         {
314 			int size = list.size();
315 			if(row + 1 > size) 
316             {
317 				// Add one to the row since it is zero based
318 				for(int i = size; i < row + 1; i++) 
319                 {
320 					list.add(null);
321 				}
322 			}
323 		}
324 	}
325 	
326 	// Ensures that there is room for the fragment at the given row
327 	// This method will insert null rows as necessary
328 	protected List makeSpace(Coordinate newCoordinate) 
329     {
330 		int newCol = newCoordinate.getNewCol();
331 		int newRow = newCoordinate.getNewRow();
332 		
333 		// Find the column. Note that a new column will never be created
334 		List column = this.columnsList[newCol];
335 		if(newRow + 1 > column.size()) 
336         {
337 			// Need to add rows
338 			for(int i = column.size(); i < newRow + 1; i++) 
339             {
340 				column.add(null);
341 			}
342 		}
343 		return column;
344 	}
345 	
346     public int addColumns( int col )
347         throws PortletPlacementException 
348     {
349         if ( col > this.numberOfColumns )
350         {            
351             if ( col < 100 ) // arbitrary limit of columns
352             {
353                 // expand
354                 int prevNumberOfColumns = this.numberOfColumns;
355                 this.numberOfColumns = col + 1;
356                 
357                 Vector [] temp = new Vector[this.numberOfColumns];
358                 for (int ix = 0; ix < prevNumberOfColumns; ix++)
359                     temp[ix] = this.columnsList[ix];
360                 for (int ix = prevNumberOfColumns; ix < temp.length; ix++)
361                     temp[ix] = new Vector();
362                 this.columnsList = temp;
363             }
364             else
365             {
366                 throw new PortletPlacementException( "cannot add column - " + col + " is above the limit of columns that this api supports" );
367             }
368         }
369         return col;
370     }
371 
372 	public Coordinate add(Fragment fragment, Coordinate coordinate) throws PortletPlacementException 
373     {
374         int col = coordinate.getNewCol();
375         int row = coordinate.getNewRow();
376         
377         if (this.numberOfColumns == -1)
378         {
379             this.numberOfColumns = 1;
380             this.columnsList = new Vector[this.numberOfColumns];
381             col = 0;
382         }        
383         if (col > this.numberOfColumns)
384         {    
385             col = addColumns( col );
386         }
387         
388         Vector column = this.columnsList[col];
389         if (column != null)
390         {
391             for (int ix = 0; ix < column.size(); ix++)
392             {                
393                 Fragment frag = (Fragment)column.get(ix);
394                 if (frag == null)
395                     continue;
396                 Coordinate c = (Coordinate)this.fragmentCoordinateMap.get(frag);
397                 if (c == null)
398                     continue;
399                 if (c.getNewCol() == row)
400                 {
401                     row++;
402                 }
403                 
404             }
405             // Make sure that the column has room to add the row
406             if(row < 0 || row > column.size()) {
407             	// Add to the end
408             	column.addElement(fragment);
409             	row = column.size()-1;
410             } else {
411             	column.add(row, fragment);
412             }
413             Coordinate newCoord = new CoordinateImpl(col, row, col, row);
414             this.fragmentCoordinateMap.put(fragment, newCoord);
415             return newCoord;
416         }
417         return coordinate;
418 	}
419 	
420 	// Adds an existing fragment to the coordinate position
421 	protected Coordinate addInternal(Fragment fragment, Coordinate coordinate) 
422     throws PortletPlacementException 
423     {
424 		int newCol = coordinate.getNewCol();
425 		int newRow = coordinate.getNewRow();
426 		
427 		// Check to see if the column exists
428 		if(newCol < 0 || newCol > this.columnsList.length) 
429         {
430 			throw new PortletPlacementException("column out of bounds" + fragment.getName());
431 		}
432 		
433 		Vector columnArray = this.columnsList[newCol];
434 
435 		// Make sure the list has enough room for the set
436 		prepareList(columnArray, newRow);
437 		
438 		columnArray.setElementAt(fragment, newRow);
439 		
440 		// Add the fragment to the hash map
441 		this.fragmentCoordinateMap.put(fragment, coordinate);
442 		
443 		return coordinate;
444 	}
445 
446 	public Fragment getFragment(String fragmentId) throws PortletPlacementException 
447     {
448 		return (Fragment)this.fragmentMap.get(fragmentId);
449 	}
450 	
451 	public Fragment getFragmentAtOldCoordinate(Coordinate coordinate) throws PortletPlacementException 
452     {
453 		return getFragmentAtCoordinate(coordinate, true);
454 	}
455 
456 	public Fragment getFragmentAtNewCoordinate(Coordinate coordinate) throws PortletPlacementException 
457     {
458 		return getFragmentAtCoordinate(coordinate, false);
459 	}
460 
461 	protected Fragment getFragmentAtCoordinate(Coordinate coordinate, boolean isOld) throws PortletPlacementException 
462     {
463 		int col = -1;
464 		int row = -1;
465 		if (isOld == true) 
466         {
467 			col = coordinate.getOldCol();
468 			row = coordinate.getOldRow();
469 		} else 
470         {
471 			col = coordinate.getNewCol();
472 			row = coordinate.getNewRow();
473 		}
474 		
475 		// Do some sanity checking about the request
476 		if(col < 0 || col > this.columnsList.length) 
477         {
478 			throw new PortletPlacementException("requested column is out of bounds");
479 		}
480 		
481 		// Get the array list associated with the column
482 		Vector columnArray = this.columnsList[col];
483 		if(row < 0 || row > columnArray.size()) 
484         {
485 			throw new PortletPlacementException("requested row is out of bounds");
486 		}
487 		
488 		return (Fragment)columnArray.get(row);
489 	}
490 	
491 	public Fragment getFragmentById(String fragmentId) throws PortletPlacementException 
492     {
493 		return (Fragment)this.fragmentMap.get(fragmentId);
494 	}
495 
496 	public int getNumberColumns() throws PortletPlacementException 
497     {
498         return numberOfColumns;
499 		//return this.columnsList.length;
500 	}
501 
502 	public int getNumberRows(int col) throws PortletPlacementException 
503     {
504 		// Sanity check the column
505 		if(col < 0 || col > this.columnsList.length) 
506         {
507 			throw new PortletPlacementException("column out of bounds");
508 		}
509 		
510 		return this.columnsList[col].size();
511 	}
512 
513 	public Coordinate moveAbsolute(Fragment fragment, Coordinate newCoordinate) 
514     throws PortletPlacementException 
515     {
516 		// Find the fragment
517 		Coordinate oldCoordinate = (Coordinate)this.fragmentCoordinateMap.get(fragment);
518 		if(oldCoordinate == null) 
519         {
520 			throw new PortletPlacementException("could not find fragment");
521 		}
522 		
523 		// Save the old coordinates
524 		int oldCol = oldCoordinate.getOldCol();
525 		int oldRow = oldCoordinate.getOldRow();
526 
527 		// Create a new coordinate object with both the old and new positions
528 		int newCol = newCoordinate.getNewCol();
529 		int newRow = newCoordinate.getNewRow();
530 		
531 		// Make sure there is a place for the move
532 		//List oldRowList = makeSpace(newCoordinate);
533 
534 		List oldRowList = this.columnsList[oldCol];
535 		
536 		// Remove the fragment from it's old position
537 		oldRowList.remove(oldRow);
538 
539 		// The next two lines must occur after the remove above.  This is
540 		// because the new and old columns might be the same and the remove
541 		// will change the number of rows
542         if (newCol > this.numberOfColumns)
543         {    
544             newCol = addColumns( newCol );
545         }
546 
547 		List newRowList = this.columnsList[newCol];
548 		int numRowsNewColumn = newRowList.size();
549 		
550 		// Decide whether an insert or an add is appropriate
551 		if(newRow > (numRowsNewColumn - 1)) 
552         {
553 			newRow = numRowsNewColumn;
554 			// Add a new row
555 			newRowList.add(fragment);
556 		} 
557         else 
558         {
559 			// Insert the fragment at the new position
560 			((Vector)newRowList).insertElementAt(fragment, newRow);		
561 		}
562 
563         //debugFragments("move absolute ");
564         
565 		// New coordinates after moving
566 		return new CoordinateImpl(oldCol, oldRow, newCol, newRow);
567 	}
568 
569 	protected Coordinate moveDirection(Fragment fragment, int deltaCol, int deltaRow) 
570     throws PortletPlacementException 
571     {
572 		// Find the fragment
573 		Coordinate foundCoordinate = (Coordinate)this.fragmentCoordinateMap.get(fragment);
574 		if(foundCoordinate == null) 
575         {
576 			throw new PortletPlacementException("could not find fragment");
577 		}
578 
579 		// Check the coordinates to make sure that there is room to move down
580 		int oldCol = foundCoordinate.getOldCol();
581 		int oldRow = foundCoordinate.getOldRow();
582 		
583 		Vector columnArray = this.columnsList[oldCol];
584 		
585 		// Check the row and column boundaries to make sure there is room
586 		// to do the move
587 		if((oldRow + deltaRow + 1 > columnArray.size()) || ((oldRow + deltaRow) < 0) ||
588 		   (oldCol + deltaCol + 1 > this.columnsList.length) || ((oldCol + deltaCol) < 0)) 
589         {
590 			// Out of bounds, don't do the move
591 			Coordinate c = new CoordinateImpl(oldCol, oldRow, oldCol, oldRow);
592             //debugFragments("move direction (1)");
593             return c;
594 		}
595         else 
596         {
597 			Coordinate c = moveAbsolute(fragment, new CoordinateImpl(oldCol, oldRow, oldCol + deltaCol, oldRow + deltaRow));
598             //debugFragments("move direction (2)");
599             return c;
600 		}        
601 	}
602 	
603 	public Coordinate moveDown(Fragment fragment) throws PortletPlacementException 
604     {
605 		return moveDirection(fragment, 0, 1);
606 	}
607 
608 	public Coordinate moveUp(Fragment fragment) throws PortletPlacementException 
609     {
610 		return moveDirection(fragment, 0, -1);
611 	}
612 
613 	public Coordinate moveLeft(Fragment fragment) throws PortletPlacementException 
614     {
615 		return moveDirection(fragment, -1, 0);
616 	}
617 
618 	public Coordinate moveRight(Fragment fragment) throws PortletPlacementException 
619     {
620 		return moveDirection(fragment, 1, 0);
621 	}
622 
623 	public Coordinate remove(Fragment fragment) throws PortletPlacementException 
624     {
625 		// Locate the fragment
626 		Coordinate coordinate = (Coordinate)this.fragmentCoordinateMap.get(fragment);
627 		if(coordinate == null) 
628         {
629 			throw new PortletPlacementException("fragment not found:" + fragment.getName());
630 		}
631 		
632 		int col = coordinate.getOldCol();
633 		int row = coordinate.getOldRow();
634 		
635 		if(col < 0 || col > this.columnsList.length) 
636         {
637 			throw new PortletPlacementException("column out of bounds:" + fragment.getName());
638 		}
639 		
640 		Vector columnArray = this.columnsList[col];
641 		if(row < 0 || row > columnArray.size()) 
642         {
643 			throw new PortletPlacementException("row out of bounds:" + fragment.getName());
644 		}
645 		
646 		// Remove the fragment from the array
647 		columnArray.remove(row);
648 		
649 		// Remove the fragment from the hashmap
650 		this.fragmentCoordinateMap.remove(fragment);
651 		this.fragmentMap.remove(fragment.getId());
652 		
653 		return coordinate;
654 	}
655 
656 }