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.rpc.atom;
020
021import org.apache.log4j.Logger;
022import org.apache.wiki.api.core.Context;
023import org.apache.wiki.api.core.Engine;
024import org.apache.wiki.api.core.Page;
025import org.apache.wiki.api.exceptions.ProviderException;
026import org.apache.wiki.api.exceptions.WikiException;
027import org.apache.wiki.api.spi.Wiki;
028import org.apache.wiki.pages.PageManager;
029import org.apache.wiki.plugin.WeblogEntryPlugin;
030import org.apache.wiki.plugin.WeblogPlugin;
031import org.apache.wiki.util.TextUtil;
032import org.intabulas.sandler.Sandler;
033import org.intabulas.sandler.SyndicationFactory;
034import org.intabulas.sandler.elements.Content;
035import org.intabulas.sandler.elements.Entry;
036import org.intabulas.sandler.elements.Feed;
037import org.intabulas.sandler.elements.Link;
038import org.intabulas.sandler.elements.Person;
039import org.intabulas.sandler.elements.impl.LinkImpl;
040import org.intabulas.sandler.exceptions.FeedMarshallException;
041
042import javax.servlet.ServletConfig;
043import javax.servlet.ServletException;
044import javax.servlet.http.HttpServlet;
045import javax.servlet.http.HttpServletRequest;
046import javax.servlet.http.HttpServletResponse;
047import java.io.IOException;
048import java.util.Collection;
049import java.util.Date;
050
051
052/**
053 *  Handles incoming requests for the Atom API.  This class uses the "sandler" Atom API implementation.
054 *
055 *  @since 2.1.97
056 */
057// FIXME: Rewrite using some other library
058public class AtomAPIServlet extends HttpServlet {
059
060    private static final Logger log = Logger.getLogger( AtomAPIServlet.class );
061
062    private static final long serialVersionUID = 0L;
063
064    private Engine m_engine;
065
066    /**
067     *  {@inheritDoc}
068     */
069    @Override
070    public void init( final ServletConfig config ) throws ServletException {
071        m_engine = Wiki.engine().find( config );
072    }
073
074    /**
075     *  Takes the name of the page from the request URI.
076     *  The initial slash is also removed.  If there is no page,
077     *  returns null.
078     */
079    private String getPageName( final HttpServletRequest request ) {
080        String name = request.getPathInfo();
081        if( name == null || name.length() <= 1 ) {
082            return null;
083        } else if( name.charAt( 0 ) == '/' ) {
084            name = name.substring( 1 );
085        }
086
087        return TextUtil.urlDecodeUTF8( name );
088    }
089
090    /**
091     *  Implements the PostURI of the Atom spec.
092     *  <p>
093     *  Implementation notes:
094     *  <ul>
095     *   <li>Only fetches the first content.  All other contents are ignored.
096     *   <li>Assumes that incoming code is plain text or WikiMarkup, not html.
097     *  </ul>
098     *  
099     *  {@inheritDoc}
100     */
101    @Override
102    public void doPost( final HttpServletRequest request, final HttpServletResponse response ) throws ServletException {
103        log.debug( "Received POST to AtomAPIServlet" );
104
105        try {
106            final String blogid = getPageName( request );
107            final Page page = m_engine.getManager( PageManager.class ).getPage( blogid );
108            if( page == null ) {
109                throw new ServletException( "Page " + blogid + " does not exist, cannot add blog post." );
110            }
111
112            // FIXME: Do authentication here
113            final Entry entry = Sandler.unmarshallEntry( request.getInputStream() );
114
115            //  Fetch the obligatory parts of the content.
116            final Content title = entry.getTitle();
117            final Content content = entry.getContent( 0 );
118            final Person author = entry.getAuthor();
119
120            // FIXME: Sandler 0.5 does not support generator
121            // Generate new blog entry.
122            final WeblogEntryPlugin plugin = new WeblogEntryPlugin();
123            final String pageName = plugin.getNewEntryPage( m_engine, blogid );
124            final String username = author.getName();
125            final Page entryPage = Wiki.contents().page( m_engine, pageName );
126            entryPage.setAuthor( username );
127
128            final Context context = Wiki.context().create( m_engine, request, entryPage );
129            final StringBuilder text = new StringBuilder();
130            text.append( "!" )
131                .append( title.getBody() )
132                .append( "\n\n" )
133                .append( content.getBody() );
134            log.debug( "Writing entry: " + text );
135            m_engine.getManager( PageManager.class ).saveText( context, text.toString() );
136        } catch( final FeedMarshallException e ) {
137            log.error("Received faulty Atom entry",e);
138            throw new ServletException("Faulty Atom entry",e);
139        } catch( final IOException e ) {
140            log.error("I/O exception",e);
141            throw new ServletException("Could not get body of request",e);
142        } catch( final WikiException e ) {
143            log.error("Provider exception while posting",e);
144            throw new ServletException("JSPWiki cannot save the entry",e);
145        }
146    }
147
148    /**
149     *  Handles HTTP GET.  However, we do not respond to GET requests,
150     *  other than to show an explanatory text.
151     *  
152     *  {@inheritDoc}
153     */
154    @Override
155    public void doGet( final HttpServletRequest request, final HttpServletResponse response ) throws ServletException {
156        log.debug( "Received HTTP GET to AtomAPIServlet" );
157        final String blogid = getPageName( request );
158        log.debug( "Requested page " + blogid );
159        try {
160            if( blogid == null ) {
161                final Feed feed = listBlogs();
162                response.setContentType( "application/x.atom+xml; charset=UTF-8" );
163                response.getWriter().println( Sandler.marshallFeed( feed ) );
164            } else {
165                final Entry entry = getBlogEntry( blogid );
166                response.setContentType( "application/x.atom+xml; charset=UTF-8" );
167                response.getWriter().println( Sandler.marshallEntry( entry ) );
168            }
169            response.getWriter().flush();
170        } catch( final Exception e ) {
171            log.error( "Unable to generate response", e );
172            throw new ServletException( "Internal problem - whack Janne on the head to get a better error report", e );
173        }
174    }
175
176    private Entry getBlogEntry( final String entryid ) {
177        final Page page = m_engine.getManager( PageManager.class ).getPage( entryid );
178        final Page firstVersion = m_engine.getManager( PageManager.class ).getPage( entryid, 1 );
179        final Entry entry = SyndicationFactory.newSyndicationEntry();
180        final String pageText = m_engine.getManager( PageManager.class ).getText(page.getName());
181        final int firstLine = pageText.indexOf('\n');
182
183        String title = "";
184        if( firstLine > 0 ) {
185            title = pageText.substring( 0, firstLine );
186        }
187
188        if( title.trim().length() == 0 ) {
189            title = page.getName();
190        }
191
192        // Remove wiki formatting
193        while( title.startsWith("!") ) {
194            title = title.substring(1);
195        }
196
197        entry.setTitle( title );
198        entry.setCreated( firstVersion.getLastModified() );
199        entry.setModified( page.getLastModified() );
200        entry.setAuthor( SyndicationFactory.createPerson( page.getAuthor(), null, null ) );
201        entry.addContent( SyndicationFactory.createEscapedContent(pageText) );
202
203        return entry;
204    }
205
206    /**
207     *  Creates and outputs a full list of all available blogs
208     */
209    private Feed listBlogs() throws ProviderException {
210        final Collection< Page > pages = m_engine.getManager( PageManager.class ).getAllPages();
211        final Feed feed = SyndicationFactory.newSyndicationFeed();
212        feed.setTitle("List of blogs at this site");
213        feed.setModified( new Date() );
214
215        for( final Page p : pages ) {
216            //  List only weblogs
217            //  FIXME: Unfortunately, a weblog is not known until it has een executed once, because plugins are off during the initial startup phase.
218            log.debug( p.getName() + " = " + p.getAttribute( WeblogPlugin.ATTR_ISWEBLOG ) );
219
220            if( !( "true".equals( p.getAttribute( WeblogPlugin.ATTR_ISWEBLOG ) ) ) ) {
221                continue;
222            }
223
224            final String encodedName = TextUtil.urlEncodeUTF8( p.getName() );
225            final Context context = Wiki.context().create( m_engine, p );
226            final String title = TextUtil.replaceEntities( org.apache.wiki.rss.Feed.getSiteName( context ) );
227            final Link postlink = createLink( "service.post", m_engine.getBaseURL() + "atom/" + encodedName, title );
228            final Link editlink = createLink( "service.edit", m_engine.getBaseURL() + "atom/" + encodedName, title );
229            final Link feedlink = createLink( "service.feed", m_engine.getBaseURL() + "atom.jsp?page=" + encodedName, title );
230
231            feed.addLink( postlink );
232            feed.addLink( feedlink );
233            feed.addLink( editlink );
234        }
235
236        return feed;
237    }
238
239    private Link createLink( final String rel, final String href, final String title ) {
240        final LinkImpl link = new LinkImpl();
241        link.setRelationship( rel );
242        link.setTitle( title );
243        link.setType( "application/x.atom+xml" );
244        link.setHref( href );
245
246        return link;
247    }
248
249    /**
250     *  {@inheritDoc}
251     */
252    @Override
253    public void doDelete( final HttpServletRequest request, final HttpServletResponse response ) {
254        log.debug( "Received HTTP DELETE" );
255    }
256
257    /**
258     *  {@inheritDoc}
259     */
260    @Override
261    public void doPut( final HttpServletRequest request, final HttpServletResponse response ) {
262        log.debug( "Received HTTP PUT" );
263    }
264
265}