You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@rave.apache.org by er...@apache.org on 2013/08/05 22:40:34 UTC

svn commit: r1510718 - in /rave/trunk: rave-components/rave-core-api/src/main/java/org/apache/rave/rest/exception/ rave-components/rave-core-api/src/main/java/org/apache/rave/rest/filters/ rave-components/rave-core/src/main/java/org/apache/rave/portal/...

Author: erinnp
Date: Mon Aug  5 20:40:34 2013
New Revision: 1510718

URL: http://svn.apache.org/r1510718
Log:
Fill out controllers, add 204 response filter

Added:
    rave/trunk/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/exception/BadRequestException.java
      - copied, changed from r1509789, rave/trunk/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/exception/ResourceNotFoundException.java
    rave/trunk/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/filters/BadRequestExceptionMapper.java
      - copied, changed from r1510670, rave/trunk/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/filters/NotFoundExceptionMapper.java
    rave/trunk/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/filters/CreatedResponseFilter.java
      - copied, changed from r1510670, rave/trunk/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/filters/LocationHeaderResponseFilter.java
    rave/trunk/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/filters/NoContentResponseFilter.java
Removed:
    rave/trunk/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/filters/LocationHeaderResponseFilter.java
Modified:
    rave/trunk/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/filters/JsonWrapperResponseFilter.java
    rave/trunk/rave-components/rave-core/src/main/java/org/apache/rave/portal/service/PageService.java
    rave/trunk/rave-components/rave-core/src/main/java/org/apache/rave/rest/impl/DefaultPageResource.java
    rave/trunk/rave-components/rave-core/src/main/java/org/apache/rave/rest/impl/DefaultRegionWidgetsResource.java
    rave/trunk/rave-components/rave-core/src/main/java/org/apache/rave/rest/impl/DefaultRegionsResource.java
    rave/trunk/rave-portal-resources/src/main/webapp/WEB-INF/cxf-applicationContext.xml

Copied: rave/trunk/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/exception/BadRequestException.java (from r1509789, rave/trunk/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/exception/ResourceNotFoundException.java)
URL: http://svn.apache.org/viewvc/rave/trunk/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/exception/BadRequestException.java?p2=rave/trunk/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/exception/BadRequestException.java&p1=rave/trunk/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/exception/ResourceNotFoundException.java&r1=1509789&r2=1510718&rev=1510718&view=diff
==============================================================================
--- rave/trunk/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/exception/ResourceNotFoundException.java (original)
+++ rave/trunk/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/exception/BadRequestException.java Mon Aug  5 20:40:34 2013
@@ -22,19 +22,19 @@ package org.apache.rave.rest.exception;
 /**
  * Thrown when a required resource is not present
  */
-public class ResourceNotFoundException extends RuntimeException {
-    public ResourceNotFoundException() {
+public class BadRequestException extends RuntimeException {
+    public BadRequestException() {
     }
 
-    public ResourceNotFoundException(String s) {
+    public BadRequestException(String s) {
         super(s);
     }
 
-    public ResourceNotFoundException(String s, Throwable throwable) {
+    public BadRequestException(String s, Throwable throwable) {
         super(s, throwable);
     }
 
-    public ResourceNotFoundException(Throwable throwable) {
+    public BadRequestException(Throwable throwable) {
         super(throwable);
     }
     

Copied: rave/trunk/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/filters/BadRequestExceptionMapper.java (from r1510670, rave/trunk/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/filters/NotFoundExceptionMapper.java)
URL: http://svn.apache.org/viewvc/rave/trunk/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/filters/BadRequestExceptionMapper.java?p2=rave/trunk/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/filters/BadRequestExceptionMapper.java&p1=rave/trunk/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/filters/NotFoundExceptionMapper.java&r1=1510670&r2=1510718&rev=1510718&view=diff
==============================================================================
--- rave/trunk/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/filters/NotFoundExceptionMapper.java (original)
+++ rave/trunk/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/filters/BadRequestExceptionMapper.java Mon Aug  5 20:40:34 2013
@@ -19,6 +19,7 @@
 
 package org.apache.rave.rest.filters;
 
+import org.apache.rave.rest.exception.BadRequestException;
 import org.apache.rave.rest.exception.ResourceNotFoundException;
 import org.apache.rave.rest.model.ErrorWrapperResponse;
 
@@ -28,14 +29,13 @@ import javax.ws.rs.core.Response;
 import javax.ws.rs.ext.ExceptionMapper;
 
 @Produces(MediaType.APPLICATION_JSON)
-public class NotFoundExceptionMapper implements ExceptionMapper<ResourceNotFoundException> {
+public class BadRequestExceptionMapper implements ExceptionMapper<BadRequestException> {
 
     @Override
-    public Response toResponse(ResourceNotFoundException e) {
-        String id = e.getMessage();
-        return Response.status(Response.Status.NOT_FOUND).entity(new ErrorWrapperResponse(
-                "The requested resource could not be found.",
-                "The requested resource could not be found."
+    public Response toResponse(BadRequestException e) {
+        return Response.status(Response.Status.BAD_REQUEST).entity(new ErrorWrapperResponse(
+                e.getMessage(),
+                "The server received a bad request."
         )).build();
     }
 }

Copied: rave/trunk/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/filters/CreatedResponseFilter.java (from r1510670, rave/trunk/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/filters/LocationHeaderResponseFilter.java)
URL: http://svn.apache.org/viewvc/rave/trunk/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/filters/CreatedResponseFilter.java?p2=rave/trunk/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/filters/CreatedResponseFilter.java&p1=rave/trunk/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/filters/LocationHeaderResponseFilter.java&r1=1510670&r2=1510718&rev=1510718&view=diff
==============================================================================
--- rave/trunk/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/filters/LocationHeaderResponseFilter.java (original)
+++ rave/trunk/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/filters/CreatedResponseFilter.java Mon Aug  5 20:40:34 2013
@@ -32,7 +32,7 @@ import java.util.List;
 import javax.ws.rs.core.Response;
 
 
-public class LocationHeaderResponseFilter implements ContainerResponseFilter {
+public class CreatedResponseFilter implements ContainerResponseFilter {
 
     @Override
     public void filter(ContainerRequestContext containerRequestContext, ContainerResponseContext containerResponseContext) throws IOException {

Modified: rave/trunk/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/filters/JsonWrapperResponseFilter.java
URL: http://svn.apache.org/viewvc/rave/trunk/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/filters/JsonWrapperResponseFilter.java?rev=1510718&r1=1510717&r2=1510718&view=diff
==============================================================================
--- rave/trunk/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/filters/JsonWrapperResponseFilter.java (original)
+++ rave/trunk/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/filters/JsonWrapperResponseFilter.java Mon Aug  5 20:40:34 2013
@@ -37,18 +37,21 @@ public class JsonWrapperResponseFilter i
 
     @Override
     public void filter(ContainerRequestContext containerRequestContext, ContainerResponseContext containerResponseContext) throws IOException {
-        Object o = containerResponseContext.getEntity();
-        JsonResponseWrapper wrapper;
+        if (containerResponseContext.getStatus() == Response.Status.OK.getStatusCode()) {
 
-        Class clazz = o.getClass();
-        if (List.class.isAssignableFrom(clazz)) {
-            wrapper = new JsonResponseWrapper((List) o);
-        } else if (SearchResult.class.isAssignableFrom(clazz)) {
-            wrapper = new JsonResponseWrapper((SearchResult) o);
-        } else {
-            wrapper = new JsonResponseWrapper(o);
-        }
+            Object o = containerResponseContext.getEntity();
+            JsonResponseWrapper wrapper;
+
+            Class clazz = o.getClass();
+            if (List.class.isAssignableFrom(clazz)) {
+                wrapper = new JsonResponseWrapper((List) o);
+            } else if (SearchResult.class.isAssignableFrom(clazz)) {
+                wrapper = new JsonResponseWrapper((SearchResult) o);
+            } else {
+                wrapper = new JsonResponseWrapper(o);
+            }
 
-        containerResponseContext.setEntity(wrapper, containerResponseContext.getEntityAnnotations(), containerResponseContext.getMediaType());
+            containerResponseContext.setEntity(wrapper, containerResponseContext.getEntityAnnotations(), containerResponseContext.getMediaType());
+        }
     }
 }

Added: rave/trunk/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/filters/NoContentResponseFilter.java
URL: http://svn.apache.org/viewvc/rave/trunk/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/filters/NoContentResponseFilter.java?rev=1510718&view=auto
==============================================================================
--- rave/trunk/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/filters/NoContentResponseFilter.java (added)
+++ rave/trunk/rave-components/rave-core-api/src/main/java/org/apache/rave/rest/filters/NoContentResponseFilter.java Mon Aug  5 20:40:34 2013
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.rave.rest.filters;
+
+import org.apache.cxf.jaxrs.ext.ResponseHandler;
+import org.apache.cxf.jaxrs.model.OperationResourceInfo;
+import org.apache.cxf.message.Message;
+import org.apache.rave.rest.model.JsonResponseWrapper;
+import org.apache.rave.rest.model.SearchResult;
+
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerResponseContext;
+import javax.ws.rs.container.ContainerResponseFilter;
+import javax.ws.rs.core.Response;
+
+import java.io.IOException;
+import java.util.List;
+
+public class NoContentResponseFilter implements ContainerResponseFilter {
+
+    @Override
+    public void filter(ContainerRequestContext containerRequestContext, ContainerResponseContext containerResponseContext) throws IOException {
+        Object o = containerResponseContext.getEntity();
+
+        if(o == null) {
+            containerResponseContext.setStatus(Response.Status.NO_CONTENT.getStatusCode());
+        }
+    }
+}
\ No newline at end of file

Modified: rave/trunk/rave-components/rave-core/src/main/java/org/apache/rave/portal/service/PageService.java
URL: http://svn.apache.org/viewvc/rave/trunk/rave-components/rave-core/src/main/java/org/apache/rave/portal/service/PageService.java?rev=1510718&r1=1510717&r2=1510718&view=diff
==============================================================================
--- rave/trunk/rave-components/rave-core/src/main/java/org/apache/rave/portal/service/PageService.java (original)
+++ rave/trunk/rave-components/rave-core/src/main/java/org/apache/rave/portal/service/PageService.java Mon Aug  5 20:40:34 2013
@@ -55,7 +55,7 @@ public interface PageService {
      * @param pageId to lookup
      * @return the Page object
      */
-    @PostAuthorize("hasPermission(returnObject, 'read')")
+    @PostAuthorize("returnObject == null or hasPermission(returnObject, 'read')")
     Page getPage(String pageId);
 
     /**
@@ -73,7 +73,7 @@ public interface PageService {
      * @param userId The user to retrieve the page for.
      * @return The profile page
      */
-    @PostAuthorize("hasPermission(returnObject, 'read')")
+    @PostAuthorize("returnObject == null or hasPermission(returnObject, 'read')")
     Page getPersonProfilePage(String userId);
 
     /**

Modified: rave/trunk/rave-components/rave-core/src/main/java/org/apache/rave/rest/impl/DefaultPageResource.java
URL: http://svn.apache.org/viewvc/rave/trunk/rave-components/rave-core/src/main/java/org/apache/rave/rest/impl/DefaultPageResource.java?rev=1510718&r1=1510717&r2=1510718&view=diff
==============================================================================
--- rave/trunk/rave-components/rave-core/src/main/java/org/apache/rave/rest/impl/DefaultPageResource.java (original)
+++ rave/trunk/rave-components/rave-core/src/main/java/org/apache/rave/rest/impl/DefaultPageResource.java Mon Aug  5 20:40:34 2013
@@ -20,11 +20,12 @@
 package org.apache.rave.rest.impl;
 
 
-import org.apache.rave.exception.ResourceNotFoundException;
+import org.apache.rave.rest.exception.ResourceNotFoundException;
 import org.apache.rave.model.PageType;
 import org.apache.rave.portal.service.PageService;
 import org.apache.rave.rest.PagesResource;
 import org.apache.rave.rest.RegionsResource;
+import org.apache.rave.rest.exception.BadRequestException;
 import org.apache.rave.rest.model.*;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -46,7 +47,7 @@ public class DefaultPageResource impleme
         SearchResult<org.apache.rave.model.Page> fromDb = pageService.getAll();
         List<Page> pages = new ArrayList<Page>();
 
-        for(org.apache.rave.model.Page page : fromDb.getResultSet()) {
+        for (org.apache.rave.model.Page page : fromDb.getResultSet()) {
             pages.add(new Page(page));
         }
 
@@ -57,19 +58,26 @@ public class DefaultPageResource impleme
     @Override
     public Page createPage(Page page) {
         //TODO: RAVE-977 - when Page type enum is deprecated escape from this logic
-        if(page.getPageType() == "user") {
+        if (page.getPageType() == "user") {
+            if (page.getName() == null) {
+                throw new BadRequestException("Page name property must be defined.");
+            }
+            if (page.getPageLayoutCode() == null) {
+                throw new BadRequestException("Page pageLayoutCode property must be defined.");
+            }
             org.apache.rave.model.Page fromDb = pageService.addNewUserPage(page.getName(), page.getPageLayoutCode());
-            Page responsePage =  new Page(fromDb);
+            Page responsePage = new Page(fromDb);
 
             return responsePage;
         } else {
-            //TODO: throw 400 exception
-            return null;
+            //TODO: RAVE-977 this will change
+            throw new BadRequestException("Page pageType property must equal 'user'.");
         }
     }
 
     @Override
     public Page deletePage(String id) {
+        //TODO: this cannot return a 404
         logger.debug("Deleting page " + id);
         pageService.deletePage(id);
         return null;
@@ -80,18 +88,24 @@ public class DefaultPageResource impleme
         logger.debug("Retrieving page for export: " + id);
         org.apache.rave.model.Page fromDb = pageService.getPage(id);
         //TODO: with a bad ID a 403 gets thrown before I hit this block. Why?
-        if(fromDb == null) {
+        if (fromDb == null) {
             throw new ResourceNotFoundException(id);
         }
-        Page responsePage =  new Page(fromDb);
+        Page responsePage = new Page(fromDb);
 
         return responsePage;
     }
 
     @Override
     public Page updatePage(String id, Page page) {
+        if (page.getName() == null) {
+            throw new BadRequestException("Page name property must be defined.");
+        }
+        if (page.getPageLayoutCode() == null) {
+            throw new BadRequestException("Page pageLayoutCode property must be defined.");
+        }
         org.apache.rave.model.Page fromDb = pageService.updatePage(id, page.getName(), page.getPageLayoutCode());
-        Page responsePage =  new Page(fromDb);
+        Page responsePage = new Page(fromDb);
 
         return responsePage;
     }

Modified: rave/trunk/rave-components/rave-core/src/main/java/org/apache/rave/rest/impl/DefaultRegionWidgetsResource.java
URL: http://svn.apache.org/viewvc/rave/trunk/rave-components/rave-core/src/main/java/org/apache/rave/rest/impl/DefaultRegionWidgetsResource.java?rev=1510718&r1=1510717&r2=1510718&view=diff
==============================================================================
--- rave/trunk/rave-components/rave-core/src/main/java/org/apache/rave/rest/impl/DefaultRegionWidgetsResource.java (original)
+++ rave/trunk/rave-components/rave-core/src/main/java/org/apache/rave/rest/impl/DefaultRegionWidgetsResource.java Mon Aug  5 20:40:34 2013
@@ -21,6 +21,7 @@ package org.apache.rave.rest.impl;
 
 
 import org.apache.rave.portal.service.PageService;
+import org.apache.rave.rest.exception.ResourceNotFoundException;
 import org.apache.rave.rest.model.Page;
 import org.apache.rave.rest.model.Region;
 import org.apache.rave.rest.RegionWidgetsResource;
@@ -76,19 +77,24 @@ public class DefaultRegionWidgetsResourc
                 break;
             }
         }
-        //TODO: if match == null throw 404 exception
+
+        if(match == null) {
+            throw new ResourceNotFoundException(regionWidgetId);
+        }
 
         return match;
     }
 
     @Override
     public RegionWidget updatePageRegionRegionWidget(String regionWidgetId, RegionWidget regionWidget) {
+
         return null;  //To change body of implemented methods use File | Settings | File Templates.
     }
 
     @Override
     public RegionWidget deletePageRegionRegionWidget(String regionWidgetId) {
-        return null;  //To change body of implemented methods use File | Settings | File Templates.
+        pageService.removeWidgetFromPage(regionWidgetId);
+        return null;
     }
 
     @Inject

Modified: rave/trunk/rave-components/rave-core/src/main/java/org/apache/rave/rest/impl/DefaultRegionsResource.java
URL: http://svn.apache.org/viewvc/rave/trunk/rave-components/rave-core/src/main/java/org/apache/rave/rest/impl/DefaultRegionsResource.java?rev=1510718&r1=1510717&r2=1510718&view=diff
==============================================================================
--- rave/trunk/rave-components/rave-core/src/main/java/org/apache/rave/rest/impl/DefaultRegionsResource.java (original)
+++ rave/trunk/rave-components/rave-core/src/main/java/org/apache/rave/rest/impl/DefaultRegionsResource.java Mon Aug  5 20:40:34 2013
@@ -20,22 +20,19 @@
 package org.apache.rave.rest.impl;
 
 
+import org.apache.rave.rest.exception.BadRequestException;
 import org.apache.rave.rest.exception.ResourceNotFoundException;
 import org.apache.rave.rest.RegionWidgetsResource;
 import org.apache.rave.rest.RegionsResource;
 import org.apache.rave.rest.model.Page;
 import org.apache.rave.rest.model.Region;
 import org.apache.rave.rest.model.SearchResult;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import javax.inject.Inject;
-import javax.ws.rs.core.Response;
 import java.util.List;
 
 public class DefaultRegionsResource implements RegionsResource {
 
-    private Logger logger = LoggerFactory.getLogger(getClass());
     private Page page;
 
     private DefaultRegionWidgetsResource regionWidgetsResource;
@@ -54,8 +51,8 @@ public class DefaultRegionsResource impl
 
     @Override
     public Region createPageRegion(Region region) {
-        //TODO: this method does not make sense since regions are changed via a page's pageLayoutCode
-        return null;
+        throw new BadRequestException("Direct manipulation of Regions is not allowed. " +
+                "Too add or delete page regions, edit a page's pageLayoutCode property.");
     }
 
     @Override
@@ -78,14 +75,14 @@ public class DefaultRegionsResource impl
 
     @Override
     public Region updatePageRegion(String regionId, Region region) {
-        //TODO: this method does not make sense since regions are changed via a page's pageLayoutCode
-        return null;  //To change body of implemented methods use File | Settings | File Templates.
+        throw new BadRequestException("Direct manipulation of Regions is not allowed. " +
+                "Too add or delete page regions, edit a page's pageLayoutCode property.");
     }
 
     @Override
     public Region deletePageRegion(String regionId) {
-        //TODO: this method does not make sense since regions are changed via a page's pageLayoutCode
-        return null;  //To change body of implemented methods use File | Settings | File Templates.
+        throw new BadRequestException("Direct manipulation of Regions is not allowed. " +
+                "Too add or delete page regions, edit a page's pageLayoutCode property.");
     }
 
     @Override

Modified: rave/trunk/rave-portal-resources/src/main/webapp/WEB-INF/cxf-applicationContext.xml
URL: http://svn.apache.org/viewvc/rave/trunk/rave-portal-resources/src/main/webapp/WEB-INF/cxf-applicationContext.xml?rev=1510718&r1=1510717&r2=1510718&view=diff
==============================================================================
--- rave/trunk/rave-portal-resources/src/main/webapp/WEB-INF/cxf-applicationContext.xml (original)
+++ rave/trunk/rave-portal-resources/src/main/webapp/WEB-INF/cxf-applicationContext.xml Mon Aug  5 20:40:34 2013
@@ -42,11 +42,13 @@
             <bean class="org.apache.cxf.jaxrs.provider.JAXBElementProvider"/>
 
             <!-- Custom filters -->
-            <ref bean="LocationHeaderResponseFilter"/>
+            <ref bean="NoContentResponseFilter"/>
+            <ref bean="CreatedResponseFilter"/>
             <ref bean="JsonWrapperResponseFilter"/>
 
             <!-- Exception Mappers -->
             <ref bean="NotFoundExceptionMapper"/>
+            <ref bean="BadRequestExceptionMapper"/>
         </jaxrs:providers>
         <jaxrs:serviceBeans>
             <ref bean="peopleBean"/>
@@ -76,7 +78,10 @@
     <bean id="pagesForRenderBean" class="org.apache.rave.rest.impl.DefaultPageForRenderResource" autowire="byType" />
 
     <bean id="JsonWrapperResponseFilter" class="org.apache.rave.rest.filters.JsonWrapperResponseFilter"/>
-    <bean id="LocationHeaderResponseFilter" class="org.apache.rave.rest.filters.LocationHeaderResponseFilter"/>
+    <bean id="CreatedResponseFilter" class="org.apache.rave.rest.filters.CreatedResponseFilter"/>
+    <bean id="NoContentResponseFilter" class="org.apache.rave.rest.filters.NoContentResponseFilter"/>
+
     <bean id="NotFoundExceptionMapper" class="org.apache.rave.rest.filters.NotFoundExceptionMapper"/>
+    <bean id="BadRequestExceptionMapper" class="org.apache.rave.rest.filters.BadRequestExceptionMapper"/>
 
 </beans>
\ No newline at end of file