001/* 
002    Licensed to the Apache Software Foundation (ASF) under one
003    or more contributor license agreements.  See the NOTICE file
004    distributed with this work for additional information
005    regarding copyright ownership.  The ASF licenses this file
006    to you under the Apache License, Version 2.0 (the
007    "License"); you may not use this file except in compliance
008    with the License.  You may obtain a copy of the License at
009
010       http://www.apache.org/licenses/LICENSE-2.0
011
012    Unless required by applicable law or agreed to in writing,
013    software distributed under the License is distributed on an
014    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015    KIND, either express or implied.  See the License for the
016    specific language governing permissions and limitations
017    under the License.  
018 */
019package org.apache.wiki.tags;
020
021import org.apache.log4j.Logger;
022import org.apache.wiki.api.core.ContextEnum;
023import org.apache.wiki.api.core.Engine;
024import org.apache.wiki.api.core.Page;
025import org.apache.wiki.pages.PageManager;
026import org.apache.wiki.util.HttpUtil;
027import org.apache.wiki.util.TextUtil;
028
029import javax.servlet.http.HttpServletRequest;
030import javax.servlet.jsp.JspWriter;
031import java.io.IOException;
032import java.text.ParseException;
033import java.text.SimpleDateFormat;
034import java.util.Calendar;
035import java.util.Date;
036
037
038/**
039 *  Provides a nice calendar.  Responds to the following HTTP parameters:
040 *  <ul>
041 *  <li>calendar.date - If this parameter exists, then the calendar
042 *  date is taken from the month and year.  The date must be in ddMMyy
043 *  format.
044 *  <li>weblog.startDate - If calendar.date parameter does not exist,
045 *  we then check this date.
046 *  </ul>
047 *
048 *  If neither calendar.date nor weblog.startDate parameters exist,
049 *  then the calendar will default to the current month.
050 *
051 *  @since 2.0
052 */
053
054// FIXME: This class is extraordinarily lacking.
055public class CalendarTag extends WikiTagBase {
056
057    private static final long serialVersionUID = 0L;
058    private static final Logger log = Logger.getLogger( CalendarTag.class );
059    
060    private SimpleDateFormat m_pageFormat = null;
061    private SimpleDateFormat m_urlFormat = null;
062    private SimpleDateFormat m_monthUrlFormat = null;
063    private SimpleDateFormat m_dateFormat = new SimpleDateFormat( "ddMMyy" );
064
065    /**
066     *  {@inheritDoc}
067     */
068    @Override
069    public void initTag()
070    {
071        super.initTag();
072        m_pageFormat = m_urlFormat = m_monthUrlFormat = null;
073        m_dateFormat = new SimpleDateFormat( "ddMMyy" );
074    }
075
076    /*
077    public void setYear( String year )
078    {
079        m_year = year;
080    }
081
082    public void setMonth( String month )
083    {
084        m_month = month;
085    }
086    */
087
088    /**
089     *  Sets the page format.  If a page corresponding to the format is found when
090     *  the calendar is being rendered, a link to that page is created.  E.g. if the
091     *  format is set to <tt>'Main_blogentry_'ddMMyy</tt>, it works nicely in
092     *  conjuction to the WeblogPlugin.
093     *  
094     *  @param format The format in the SimpleDateFormat fashion.
095     *  
096     *  @see SimpleDateFormat
097     *  @see org.apache.wiki.plugin.WeblogPlugin
098     */
099    public void setPageformat( final String format )
100    {
101        m_pageFormat = new SimpleDateFormat( format );
102    }
103
104    /**
105     *  Set the URL format.  If the pageformat is not set, all dates are
106     *  links to pages according to this format.  The pageformat
107     *  takes precedence.
108     *  
109     *  @param format The URL format in the SimpleDateFormat fashion.
110     *  @see SimpleDateFormat
111     */
112    public void setUrlformat( final String format )
113    {
114        m_urlFormat = new SimpleDateFormat( format );
115    }
116
117    /**
118     *  Set the format to be used for links for the months.
119     *  
120     *  @param format The format to set in the SimpleDateFormat fashion.
121     *  
122     *  @see SimpleDateFormat
123     */
124    public void setMonthurlformat( final String format )
125    {
126        m_monthUrlFormat = new SimpleDateFormat( format );
127    }
128
129    private String format( final String txt ) {
130        final Page p = m_wikiContext.getPage();
131        if( p != null ) {
132            return TextUtil.replaceString( txt, "%p", p.getName() );
133        }
134
135        return txt;
136    }
137
138    /**
139     *  Returns a link to the given day.
140     */
141    private String getDayLink( final Calendar day ) {
142        final Engine engine = m_wikiContext.getEngine();
143        final String result;
144
145        if( m_pageFormat != null ) {
146            final String pagename = m_pageFormat.format( day.getTime() );
147            
148            if( engine.getManager( PageManager.class ).wikiPageExists( pagename ) ) {
149                if( m_urlFormat != null ) {
150                    final String url = m_urlFormat.format( day.getTime() );
151                    result = "<td class=\"link\"><a href=\""+url+"\">"+day.get( Calendar.DATE )+"</a></td>";
152                } else {
153                    result = "<td class=\"link\"><a href=\""+m_wikiContext.getViewURL( pagename )+"\">"+
154                             day.get( Calendar.DATE )+"</a></td>";
155                }
156            } else {
157                result = "<td class=\"days\">"+day.get(Calendar.DATE)+"</td>";
158            }
159        } else if( m_urlFormat != null ) {
160            final String url = m_urlFormat.format( day.getTime() );
161            result = "<td><a href=\""+url+"\">"+day.get( Calendar.DATE )+"</a></td>";
162        } else {
163            result = "<td class=\"days\">"+day.get(Calendar.DATE)+"</td>";
164        }
165
166        return format( result );
167    }
168
169    private String getMonthLink( final Calendar day ) {
170        final SimpleDateFormat monthfmt = new SimpleDateFormat( "MMMM yyyy" );
171        final String result;
172
173        if( m_monthUrlFormat == null ) {
174            result = monthfmt.format( day.getTime() );
175        } else {
176            final Calendar cal = (Calendar)day.clone();
177            final int firstDay = cal.getActualMinimum( Calendar.DATE );
178            final int lastDay  = cal.getActualMaximum( Calendar.DATE );
179
180            cal.set( Calendar.DATE, lastDay );
181            String url = m_monthUrlFormat.format( cal.getTime() );
182
183            url = TextUtil.replaceString( url, "%d", Integer.toString( lastDay - firstDay + 1 ) );
184
185            result = "<a href=\""+url+"\">"+monthfmt.format(cal.getTime())+"</a>";
186        }
187
188        return format( result );
189    }
190
191    private String getMonthNaviLink( final Calendar day, final String txt, String queryString ) {
192        final String result;
193        queryString = TextUtil.replaceEntities( queryString );
194        final Calendar nextMonth = Calendar.getInstance();
195        nextMonth.set( Calendar.DATE, 1 );  
196        nextMonth.add( Calendar.DATE, -1);
197        nextMonth.add( Calendar.MONTH, 1 ); // Now move to 1st day of next month
198
199        if ( day.before( nextMonth ) ) {
200            final Page thePage = m_wikiContext.getPage();
201            final String pageName = thePage.getName();
202
203            final String calendarDate = m_dateFormat.format( day.getTime() );
204            String url = m_wikiContext.getURL( ContextEnum.PAGE_VIEW.getRequestContext(), pageName,"calendar.date="+calendarDate );
205
206            if( queryString != null && queryString.length() > 0 ) {
207                //
208                // Ensure that the 'calendar.date=ddMMyy' has been removed from the queryString
209                //
210
211                // FIXME: Might be useful to have an entire library of 
212                //        routines for this.  Will fail if it's not calendar.date 
213                //        but something else.
214
215                final int pos1 = queryString.indexOf("calendar.date=");
216                if( pos1 >= 0 ) {
217                    String tmp = queryString.substring( 0, pos1 );
218                    // FIXME: Will this fail when we use & instead of &amp?
219                    // FIXME: should use some parsing routine
220                    final int pos2 = queryString.indexOf("&", pos1 ) + 1;
221                    if ( ( pos2 > 0 ) && ( pos2 < queryString.length() ) ) {
222                        tmp = tmp + queryString.substring(pos2);
223                    }
224                    queryString = tmp;
225                }
226
227                if( queryString.length() > 0 ) {
228                    url = url + "&amp;" + queryString;
229                }
230            }
231            result = "<td><a href=\""+url+"\">"+txt+"</a></td>";
232        } else {
233            result="<td> </td>";
234        }    
235
236        return format( result );
237    }
238
239    /**
240     *  {@inheritDoc}
241     */
242    @Override
243    public final int doWikiStartTag() throws IOException {
244        final Engine engine = m_wikiContext.getEngine();
245        final JspWriter out = pageContext.getOut();
246        final Calendar cal = Calendar.getInstance();
247        final Calendar prevCal = Calendar.getInstance();
248        final Calendar nextCal = Calendar.getInstance();
249
250        //
251        //  Check if there is a parameter in the request to set the date.
252        //
253        String calendarDate = pageContext.getRequest().getParameter( "calendar.date" );
254        if( calendarDate == null ) {
255            calendarDate = pageContext.getRequest().getParameter( "weblog.startDate" );
256        }
257        
258        if( calendarDate != null ) {
259            try {
260                final Date d = m_dateFormat.parse( calendarDate );
261                cal.setTime( d );
262                prevCal.setTime( d );
263                nextCal.setTime( d );
264            } catch( final ParseException e ) {
265                log.warn( "date format wrong: " + calendarDate );
266            }
267        }
268
269        cal.set( Calendar.DATE, 1 );     // First, set to first day of month
270        prevCal.set( Calendar.DATE, 1 );
271        nextCal.set( Calendar.DATE, 1 );
272
273        prevCal.add(Calendar.MONTH, -1); // Now move to first day of previous month
274        nextCal.add(Calendar.MONTH, 1);  // Now move to first day of next month
275
276        out.write( "<table class=\"calendar\">\n" );
277
278        final HttpServletRequest httpServletRequest = m_wikiContext.getHttpRequest();
279        final String queryString = HttpUtil.safeGetQueryString( httpServletRequest, engine.getContentEncoding() );
280        out.write( "<tr>"+
281                   getMonthNaviLink(prevCal,"&lt;&lt;", queryString)+
282                   "<td colspan=5 class=\"month\">"+
283                   getMonthLink( cal )+
284                   "</td>"+
285                   getMonthNaviLink(nextCal,"&gt;&gt;", queryString)+ 
286                   "</tr>\n"
287                 );
288
289        final int month = cal.get( Calendar.MONTH );
290        cal.set( Calendar.DAY_OF_WEEK, Calendar.MONDAY ); // Then, find the first day of the week.
291
292        out.write( "<tr><td class=\"weekdays\">Mon</td>"+
293                   "<td class=\"weekdays\">Tue</td>"+
294                   "<td class=\"weekdays\">Wed</td>"+
295                   "<td class=\"weekdays\">Thu</td>"+
296                   "<td class=\"weekdays\">Fri</td>"+
297                   "<td class=\"weekdays\">Sat</td>"+
298                   "<td class=\"weekdays\">Sun</td></tr>\n" );
299
300        boolean noMoreDates = false;
301        while( !noMoreDates ) {
302            out.write( "<tr>" );
303            
304            for( int i = 0; i < 7; i++ ) {
305                final int mth = cal.get( Calendar.MONTH );
306
307                if( mth != month ) {
308                    out.write("<td class=\"othermonth\">"+cal.get(Calendar.DATE)+"</td>");
309                } else {
310                    out.write( getDayLink(cal) );
311                }
312
313                cal.add( Calendar.DATE, 1 );
314            }
315
316            if( cal.get( Calendar.MONTH ) != month ) {
317                noMoreDates = true;
318            }
319
320            out.write( "</tr>\n" );
321        }
322
323        out.write( "</table>\n" );
324
325        return EVAL_BODY_INCLUDE;
326    }
327
328}