DefaultEmbeddedServer.java
/*
* #%L
* *********************************************************************************************************************
*
* NorthernWind - lightweight CMS
* http://northernwind.tidalwave.it - git clone git@bitbucket.org:tidalwave/northernwind-rca-src.git
* %%
* Copyright (C) 2013 - 2021 Tidalwave s.a.s. (http://tidalwave.it)
* %%
* *********************************************************************************************************************
*
* Licensed 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.
*
* *********************************************************************************************************************
*
*
* *********************************************************************************************************************
* #L%
*/
package it.tidalwave.northernwind.rca.embeddedserver.impl;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.HashMap;
import java.util.Map;
import java.io.IOException;
import java.io.DataInputStream;
import java.io.FileNotFoundException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.google.common.io.ByteStreams;
import lombok.RequiredArgsConstructor;
import org.springframework.core.io.ClassPathResource;
import org.eclipse.jetty.server.Server;
import it.tidalwave.messagebus.annotation.ListensTo;
import it.tidalwave.messagebus.annotation.SimpleMessageSubscriber;
import it.tidalwave.northernwind.core.impl.filter.LibraryLinkMacroFilter;
import it.tidalwave.northernwind.core.impl.filter.MediaLinkMacroFilter;
import it.tidalwave.northernwind.core.model.MimeTypeResolver;
import it.tidalwave.northernwind.core.model.ResourceFile;
import it.tidalwave.northernwind.core.model.ResourceFileSystem;
import it.tidalwave.northernwind.rca.embeddedserver.EmbeddedServer;
import it.tidalwave.northernwind.rca.ui.event.OpenSiteEvent;
import lombok.Cleanup;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import static java.util.stream.Collectors.joining;
/***********************************************************************************************************************
*
* @author Fabrizio Giudici
*
**********************************************************************************************************************/
@SimpleMessageSubscriber @RequiredArgsConstructor @Slf4j
public class DefaultEmbeddedServer implements EmbeddedServer
{
@Nonnull
private final MimeTypeResolver mimeTypeResolver;
@Getter @Setter
private int port = 12345;
@CheckForNull
/* visible for testing */ Server server;
private final Map<String, Document> documentMapByUrl = new HashMap<>();
@CheckForNull
private ResourceFileSystem fileSystem;
/*******************************************************************************************************************
*
*
*
******************************************************************************************************************/
private final ServletAdapter servlet = new ServletAdapter()
{
private static final long serialVersionUID = -2887261966375531858L;
private final MediaLinkMacroFilter mediaLinkMacroFilter = new MediaLinkMacroFilter();
private final LibraryLinkMacroFilter libraryLinkMacroFilter = new LibraryLinkMacroFilter();
@Override
protected void doGet (final @Nonnull HttpServletRequest request,
final @Nonnull HttpServletResponse response)
throws ServletException, IOException
{
String uri = request.getRequestURI();
log.debug("doGet({})", uri);
// FIXME: use a pipeline for handling those requests - eventually integrate support already in Site
uri = mediaLinkMacroFilter.filter(uri, "");
uri = libraryLinkMacroFilter.filter(uri, "");
uri = uri.replace("//", "/"); // Aloha puts a leading / before the macro
log.debug(">>> filtered {}", uri);
if (uri.startsWith("/nwa/")) // FIXME - and use ResourcePath
{
serveEditorResources(uri, response);
}
else if (uri.startsWith("/library/") || uri.startsWith("/media/")) // FIXME - and use ResourcePath
{
serveContentResources(uri, response);
}
else
{
serveRegisteredResources(uri, response);
}
}
@Override
protected void doPut (final @Nonnull HttpServletRequest request,
final @Nonnull HttpServletResponse response)
throws ServletException, IOException
{
final String uri = request.getRequestURI();
log.debug("doPut({})", uri);
updateRegisteredResource(request, response);
}
};
/*******************************************************************************************************************
*
*
*
*
******************************************************************************************************************/
/* visible for testing */ void onOpenSite (final @ListensTo @Nonnull OpenSiteEvent event)
{
log.debug("onOpenSite({})", event);
fileSystem = event.getFileSystem();
}
/*******************************************************************************************************************
*
*
*
*
******************************************************************************************************************/
@PostConstruct
public void start()
{
try
{
log.info("Starting webserver on port {}...", port);
server = new Server(port);
server.setHandler(servlet.asHandler());
server.start();
log.info(">>>> started");
}
catch (Exception e)
{
log.error("", e);
}
}
/*******************************************************************************************************************
*
*
*
*
******************************************************************************************************************/
@PreDestroy
public void stop()
{
try
{
if ((server != null) && !server.isStopping() && !server.isStopped())
{
log.info("Stopping webserver...");
server.stop();
log.info(">>>> stopped");
}
}
catch (Exception e)
{
log.error("", e);
}
}
/*******************************************************************************************************************
*
*
*
*
******************************************************************************************************************/
@Override @Nonnull
public String putDocument (final @Nonnull String path, final @Nonnull Document document)
{
documentMapByUrl.put(path, document);
return String.format("http://localhost:%d%s", port, path);
}
/*******************************************************************************************************************
*
*
*
******************************************************************************************************************/
private void serveEditorResources (final @Nonnull String uri,
final @Nonnull HttpServletResponse response)
throws IOException
{
try
{
final byte[] resource = loadResource(uri);
response.setCharacterEncoding("");
response.setContentType(mimeTypeResolver.getMimeType(uri));
response.setStatus(HttpServletResponse.SC_OK);
response.getOutputStream().write(resource);
}
catch (FileNotFoundException e)
{
log.warn("2 - Not found: {}", uri);
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
}
}
/*******************************************************************************************************************
*
*
*
******************************************************************************************************************/
private void serveContentResources (final @Nonnull String uri,
final @Nonnull HttpServletResponse response)
throws IOException
{
log.debug("serveLibraryResources({})", uri);
// don't bother when there's no opened Site
if (fileSystem == null)
{
response.setStatus(HttpServletResponse.SC_OK);
}
else
{
final ResourceFile file = fileSystem.findFileByPath("/content" + uri); // FIXME
if (file == null)
{
log.warn("5 - Not found: {}", "/content" + uri);
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
final String mimeType = file.getMimeType();
response.setContentType(mimeType);
response.setStatus(HttpServletResponse.SC_OK);
if (mimeType.startsWith("image"))
{
response.getOutputStream().write(file.asBytes());
}
else
{
response.setCharacterEncoding("UTF-8");
response.getWriter().write(file.asText("UTF-8"));
}
}
}
/*******************************************************************************************************************
*
*
*
******************************************************************************************************************/
private void serveRegisteredResources (final @Nonnull String uri,
final @Nonnull HttpServletResponse response)
throws IOException
{
final Document document = documentMapByUrl.get(uri);
if (document == null)
{
log.warn("1 - Not found: {}", uri);
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
}
else
{
response.setCharacterEncoding("UTF-8");
response.setContentType(document.getMimeType());
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().write(document.getContent());
}
}
/*******************************************************************************************************************
*
*
*
******************************************************************************************************************/
private void updateRegisteredResource (final @Nonnull HttpServletRequest request,
final @Nonnull HttpServletResponse response)
throws IOException
{
final String uri = request.getRequestURI();
final String body = request.getReader().lines().collect(joining("\n"));
final Document document = documentMapByUrl.get(uri);
if (document == null)
{
log.warn("3 - Not found: {}", uri);
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
}
else
{
document.update(body);
response.setStatus(HttpServletResponse.SC_OK);
}
}
/*******************************************************************************************************************
*
*
*
******************************************************************************************************************/
@Nonnull
/* visible for testing */ byte[] loadResource (final @Nonnull String path)
throws IOException
{
final ClassPathResource resource = new ClassPathResource(path);
final @Cleanup DataInputStream is = new DataInputStream(resource.getInputStream());
return ByteStreams.toByteArray(is);
}
}