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