View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.chemistry.opencmis.server.impl.atompub;
20  
21  import org.apache.chemistry.opencmis.commons.data.ObjectData;
22  import org.apache.chemistry.opencmis.commons.data.ObjectList;
23  import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships;
24  import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException;
25  import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException;
26  import org.apache.chemistry.opencmis.commons.impl.Constants;
27  import static org.apache.chemistry.opencmis.commons.impl.Converter.convert;
28  import org.apache.chemistry.opencmis.commons.impl.JaxBHelper;
29  import org.apache.chemistry.opencmis.commons.impl.UrlBuilder;
30  import org.apache.chemistry.opencmis.commons.impl.jaxb.CmisObjectType;
31  import org.apache.chemistry.opencmis.commons.impl.jaxb.CmisQueryType;
32  import org.apache.chemistry.opencmis.commons.server.CallContext;
33  import org.apache.chemistry.opencmis.commons.server.CmisService;
34  import org.apache.chemistry.opencmis.commons.spi.Holder;
35  import static org.apache.chemistry.opencmis.server.impl.atompub.AtomPubUtils.RESOURCE_CHANGES;
36  import static org.apache.chemistry.opencmis.server.impl.atompub.AtomPubUtils.RESOURCE_QUERY;
37  import static org.apache.chemistry.opencmis.server.impl.atompub.AtomPubUtils.compileBaseUrl;
38  import static org.apache.chemistry.opencmis.server.impl.atompub.AtomPubUtils.compileUrlBuilder;
39  import static org.apache.chemistry.opencmis.server.impl.atompub.AtomPubUtils.writeContentChangesObjectEntry;
40  import static org.apache.chemistry.opencmis.server.shared.HttpUtils.getBigIntegerParameter;
41  import static org.apache.chemistry.opencmis.server.shared.HttpUtils.getBooleanParameter;
42  import static org.apache.chemistry.opencmis.server.shared.HttpUtils.getEnumParameter;
43  import static org.apache.chemistry.opencmis.server.shared.HttpUtils.getStringParameter;
44  
45  import java.math.BigInteger;
46  
47  import java.util.GregorianCalendar;
48  
49  import javax.servlet.http.HttpServletRequest;
50  import javax.servlet.http.HttpServletResponse;
51  
52  import javax.xml.bind.JAXBElement;
53  import javax.xml.bind.Unmarshaller;
54  
55  
56  /**
57   * Discovery Service operations.
58   */
59  public class DiscoveryService
60  {
61      private static final String METHOD_GET = "GET";
62      private static final String METHOD_POST = "POST";
63  
64      private DiscoveryService(  )
65      {
66      }
67  
68      /**
69       * Query.
70       */
71      public static void query( CallContext context, CmisService service, String repositoryId,
72          HttpServletRequest request, HttpServletResponse response )
73          throws Exception
74      {
75          // get parameters
76          String statement = null;
77          Boolean searchAllVersions = null;
78          Boolean includeAllowableActions = null;
79          IncludeRelationships includeRelationships = null;
80          String renditionFilter = null;
81          BigInteger maxItems = null;
82          BigInteger skipCount = null;
83  
84          int statusCode = 0;
85  
86          if ( METHOD_POST.equals( request.getMethod(  ) ) )
87          {
88              // POST -> read from stream
89              Object queryRequest = null;
90  
91              try
92              {
93                  Unmarshaller u = JaxBHelper.createUnmarshaller(  );
94                  queryRequest = u.unmarshal( request.getInputStream(  ) );
95              }
96              catch ( Exception e )
97              {
98                  throw new CmisInvalidArgumentException( "Invalid query request: " + e, e );
99              }
100 
101             if ( !( queryRequest instanceof JAXBElement<?> ) )
102             {
103                 throw new CmisInvalidArgumentException( "Not a query document!" );
104             }
105 
106             if ( !( ( (JAXBElement<?>) queryRequest ).getValue(  ) instanceof CmisQueryType ) )
107             {
108                 throw new CmisInvalidArgumentException( "Not a query document!" );
109             }
110 
111             CmisQueryType queryType = (CmisQueryType) ( (JAXBElement<?>) queryRequest ).getValue(  );
112 
113             statement = queryType.getStatement(  );
114             searchAllVersions = queryType.isSearchAllVersions(  );
115             includeAllowableActions = queryType.isIncludeAllowableActions(  );
116             includeRelationships = convert( IncludeRelationships.class, queryType.getIncludeRelationships(  ) );
117             renditionFilter = queryType.getRenditionFilter(  );
118             maxItems = queryType.getMaxItems(  );
119             skipCount = queryType.getSkipCount(  );
120 
121             statusCode = HttpServletResponse.SC_CREATED;
122         }
123         else if ( METHOD_GET.equals( request.getMethod(  ) ) )
124         {
125             // GET -> parameters
126             statement = getStringParameter( request, Constants.PARAM_Q );
127             searchAllVersions = getBooleanParameter( request, Constants.PARAM_SEARCH_ALL_VERSIONS );
128             includeAllowableActions = getBooleanParameter( request, Constants.PARAM_ALLOWABLE_ACTIONS );
129             includeRelationships = getEnumParameter( request, Constants.PARAM_RELATIONSHIPS, IncludeRelationships.class );
130             renditionFilter = null;
131             maxItems = getBigIntegerParameter( request, Constants.PARAM_MAX_ITEMS );
132             skipCount = getBigIntegerParameter( request, Constants.PARAM_SKIP_COUNT );
133 
134             statusCode = HttpServletResponse.SC_OK;
135         }
136         else
137         {
138             throw new CmisRuntimeException( "Invalid HTTP method!" );
139         }
140 
141         // execute
142         ObjectList results = service.query( repositoryId, statement, searchAllVersions, includeAllowableActions,
143                 includeRelationships, renditionFilter, maxItems, skipCount, null );
144 
145         if ( results == null )
146         {
147             throw new CmisRuntimeException( "Results are null!" );
148         }
149 
150         // set headers
151         UrlBuilder baseUrl = compileBaseUrl( request, repositoryId );
152 
153         UrlBuilder pagingUrl = compileUrlBuilder( baseUrl, RESOURCE_QUERY, null );
154         pagingUrl.addParameter( Constants.PARAM_Q, statement );
155         pagingUrl.addParameter( Constants.PARAM_SEARCH_ALL_VERSIONS, searchAllVersions );
156         pagingUrl.addParameter( Constants.PARAM_ALLOWABLE_ACTIONS, includeAllowableActions );
157         pagingUrl.addParameter( Constants.PARAM_RELATIONSHIPS, includeRelationships );
158 
159         UrlBuilder location = new UrlBuilder( pagingUrl );
160         location.addParameter( Constants.PARAM_MAX_ITEMS, maxItems );
161         location.addParameter( Constants.PARAM_SKIP_COUNT, skipCount );
162 
163         response.setStatus( statusCode );
164         response.setContentType( Constants.MEDIATYPE_FEED );
165 
166         // The Content-Location header is optional (CMIS specification 3.7.2.1).
167         // Since it can cause problems with long query statements it is
168         // deactivated.
169         // response.setHeader("Content-Location", location.toString());
170 
171         // The Location header is not optional (CMIS specification 3.7.2.1).
172         response.setHeader( "Location", location.toString(  ) );
173 
174         // write XML
175         AtomFeed feed = new AtomFeed(  );
176         feed.startDocument( response.getOutputStream(  ) );
177         feed.startFeed( true );
178 
179         // write basic Atom feed elements
180         GregorianCalendar now = new GregorianCalendar(  );
181         feed.writeFeedElements( "query", "", "Query", now, null, results.getNumItems(  ) );
182 
183         // write links
184         feed.writeServiceLink( baseUrl.toString(  ), repositoryId );
185 
186         feed.writePagingLinks( pagingUrl, maxItems, skipCount, results.getNumItems(  ), results.hasMoreItems(  ),
187             AtomPubUtils.PAGE_SIZE );
188 
189         if ( results.getObjects(  ) != null )
190         {
191             AtomEntry entry = new AtomEntry( feed.getWriter(  ) );
192             int idCounter = 0;
193 
194             for ( ObjectData result : results.getObjects(  ) )
195             {
196                 if ( result == null )
197                 {
198                     continue;
199                 }
200 
201                 idCounter++;
202                 writeQueryResultEntry( entry, result, "id-" + idCounter, now );
203             }
204         }
205 
206         // we are done
207         feed.endFeed(  );
208         feed.endDocument(  );
209     }
210 
211     private static void writeQueryResultEntry( AtomEntry entry, ObjectData result, String id, GregorianCalendar now )
212         throws Exception
213     {
214         CmisObjectType resultJaxb = convert( result );
215 
216         if ( resultJaxb == null )
217         {
218             return;
219         }
220 
221         // start
222         entry.startEntry( false );
223 
224         // write Atom base tags
225         entry.writeAuthor( "" );
226         entry.writeId( entry.generateAtomId( id ) );
227         entry.writePublished( now );
228         entry.writeTitle( "Query Result " + id );
229         entry.writeUpdated( now );
230 
231         // write query result object
232         JaxBHelper.marshal( JaxBHelper.CMIS_EXTRA_OBJECT_FACTORY.createObject( resultJaxb ), entry.getWriter(  ), true );
233 
234         // we are done
235         entry.endEntry(  );
236     }
237 
238     /**
239      * Get content changes.
240      */
241     public static void getContentChanges( CallContext context, CmisService service, String repositoryId,
242         HttpServletRequest request, HttpServletResponse response )
243         throws Exception
244     {
245         // get parameters
246         String changeLogToken = getStringParameter( request, Constants.PARAM_CHANGE_LOG_TOKEN );
247         Boolean includeProperties = getBooleanParameter( request, Constants.PARAM_PROPERTIES );
248         String filter = getStringParameter( request, Constants.PARAM_FILTER );
249         Boolean includePolicyIds = getBooleanParameter( request, Constants.PARAM_POLICY_IDS );
250         Boolean includeAcl = getBooleanParameter( request, Constants.PARAM_ACL );
251         BigInteger maxItems = getBigIntegerParameter( request, Constants.PARAM_MAX_ITEMS );
252 
253         // execute
254         Holder<String> changeLogTokenHolder = new Holder<String>( changeLogToken );
255         ObjectList changes = service.getContentChanges( repositoryId, changeLogTokenHolder, includeProperties, filter,
256                 includePolicyIds, includeAcl, maxItems, null );
257 
258         if ( changes == null )
259         {
260             throw new CmisRuntimeException( "Changes are null!" );
261         }
262 
263         // set headers
264         response.setStatus( HttpServletResponse.SC_OK );
265         response.setContentType( Constants.MEDIATYPE_FEED );
266 
267         // write XML
268         AtomFeed feed = new AtomFeed(  );
269         feed.startDocument( response.getOutputStream(  ) );
270         feed.startFeed( true );
271 
272         // write basic Atom feed elements
273         GregorianCalendar now = new GregorianCalendar(  );
274         feed.writeFeedElements( "contentChanges", "", "Content Change", now, null, changes.getNumItems(  ) );
275 
276         // write links
277         UrlBuilder baseUrl = compileBaseUrl( request, repositoryId );
278 
279         feed.writeServiceLink( baseUrl.toString(  ), repositoryId );
280 
281         if ( changeLogTokenHolder.getValue(  ) != null )
282         {
283             UrlBuilder nextLink = compileUrlBuilder( baseUrl, RESOURCE_CHANGES, null );
284             nextLink.addParameter( Constants.PARAM_CHANGE_LOG_TOKEN, changeLogTokenHolder.getValue(  ) );
285             nextLink.addParameter( Constants.PARAM_PROPERTIES, includeProperties );
286             nextLink.addParameter( Constants.PARAM_FILTER, filter );
287             nextLink.addParameter( Constants.PARAM_POLICY_IDS, includePolicyIds );
288             nextLink.addParameter( Constants.PARAM_ACL, includeAcl );
289             nextLink.addParameter( Constants.PARAM_MAX_ITEMS, maxItems );
290             feed.writeNextLink( nextLink.toString(  ) );
291         }
292 
293         // write entries
294         if ( changes.getObjects(  ) != null )
295         {
296             AtomEntry entry = new AtomEntry( feed.getWriter(  ) );
297 
298             for ( ObjectData object : changes.getObjects(  ) )
299             {
300                 if ( object == null )
301                 {
302                     continue;
303                 }
304 
305                 writeContentChangesObjectEntry( service, entry, object, null, repositoryId, null, null, baseUrl, false );
306             }
307         }
308 
309         // we are done
310         feed.endFeed(  );
311         feed.endDocument(  );
312     }
313 }