content: /*
   This file is part of Airsonic.
  
   Airsonic is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.
  
   Airsonic is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
  
   You should have received a copy of the GNU General Public License
   along with Airsonic.  If not, see <http://www.gnu.org/licenses/>.
  
   Copyright 2016 (C) Airsonic Authors
   Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
   */
  package org.airsonic.player.controller;
  
  import org.airsonic.player.util.FileUtil;
  import org.airsonic.player.util.StringUtil;
  import org.eclipse.persistence.jaxb.JAXBContext;
  import org.eclipse.persistence.jaxb.MarshallerProperties;
  import org.jdom2.Attribute;
  import org.jdom2.Document;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import org.subsonic.restapi.Error;
  import org.subsonic.restapi.ObjectFactory;
  import org.subsonic.restapi.Response;
  import org.subsonic.restapi.ResponseStatus;
  
  import javax.servlet.http.HttpServletRequest;
  import javax.servlet.http.HttpServletResponse;
  import javax.xml.bind.JAXBException;
  import javax.xml.bind.Marshaller;
  import javax.xml.datatype.DatatypeFactory;
  import javax.xml.datatype.XMLGregorianCalendar;
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.StringWriter;
  import java.util.Calendar;
  import java.util.Date;
  import java.util.GregorianCalendar;
  
  import static org.airsonic.player.util.XMLUtil.createSAXBuilder;
  import static org.springframework.web.bind.ServletRequestUtils.getStringParameter;
  
  /**
   * @author Sindre Mehus
   * @version $Id$
   */
  public class JAXBWriter {
  
      private static final Logger LOG = LoggerFactory.getLogger(JAXBWriter.class);
  
      private final javax.xml.bind.JAXBContext jaxbContext;
      private final DatatypeFactory datatypeFactory;
      private final String restProtocolVersion;
  
      public JAXBWriter() {
          try {
              jaxbContext = JAXBContext.newInstance(Response.class);
              datatypeFactory = DatatypeFactory.newInstance();
              restProtocolVersion = getRESTProtocolVersion();
          } catch (Exception x) {
              throw new RuntimeException(x);
          }
      }
  
      private Marshaller createXmlMarshaller() {
          Marshaller marshaller = null;
          try {
              marshaller = jaxbContext.createMarshaller();
              marshaller.setProperty(Marshaller.JAXB_ENCODING, StringUtil.ENCODING_UTF8);
              marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
              return marshaller;
          } catch (JAXBException e) {
              throw new RuntimeException(e);
          }
      }
  
      private Marshaller createJsonMarshaller() {
          try {
              Marshaller marshaller;
              marshaller = jaxbContext.createMarshaller();
              marshaller.setProperty(Marshaller.JAXB_ENCODING, StringUtil.ENCODING_UTF8);
              marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
              marshaller.setProperty(MarshallerProperties.MEDIA_TYPE, "application/json");
              marshaller.setProperty(MarshallerProperties.JSON_INCLUDE_ROOT, true);
              return marshaller;
          } catch (JAXBException e) {
              throw new RuntimeException(e);
          }
      }
  
      private String getRESTProtocolVersion() throws Exception {
          InputStream in = null;
          try {
              in = StringUtil.class.getResourceAsStream("/subsonic-rest-api.xsd");
              Document document = createSAXBuilder().build(in);
              Attribute version = document.getRootElement().getAttribute("version");
              return version.getValue();
          } finally {
              FileUtil.closeQuietly(in);
          }
      }
  
      public String getRestProtocolVersion() {
          return restProtocolVersion;
      }
  
      public Response createResponse(boolean ok) {
          Response response = new ObjectFactory().createResponse();
          response.setStatus(ok ? ResponseStatus.OK : ResponseStatus.FAILED);
          response.setVersion(restProtocolVersion);
          return response;
      }
  
      public void writeResponse(HttpServletRequest request, HttpServletResponse httpResponse, Response jaxbResponse) {
  
          String format = getStringParameter(request, "f", "xml");
          String jsonpCallback = request.getParameter("callback");
          boolean json = "json".equals(format);
          boolean jsonp = "jsonp".equals(format) && jsonpCallback != null;
          Marshaller marshaller;
  
          if (json) {
              marshaller = createJsonMarshaller();
              httpResponse.setContentType("application/json");
          } else if (jsonp) {
              marshaller = createJsonMarshaller();
              httpResponse.setContentType("text/javascript");
          } else {
              marshaller = createXmlMarshaller();
              httpResponse.setContentType("text/xml");
          }
  
          httpResponse.setCharacterEncoding(StringUtil.ENCODING_UTF8);
  
          try {
              StringWriter writer = new StringWriter();
              if (jsonp) {
                  writer.append(jsonpCallback).append('(');
              }
              marshaller.marshal(new ObjectFactory().createSubsonicResponse(jaxbResponse), writer);
              if (jsonp) {
                  writer.append(");");
              }
              httpResponse.getWriter().append(writer.getBuffer());
          } catch (JAXBException | IOException x) {
              LOG.error("Failed to marshal JAXB", x);
              throw new RuntimeException(x);
          }
      }
  
      public void writeErrorResponse(HttpServletRequest request, HttpServletResponse response,
                                     SubsonicRESTController.ErrorCode code, String message) {
          Response res = createResponse(false);
          Error error = new Error();
          res.setError(error);
          error.setCode(code.getCode());
          error.setMessage(message);
          writeResponse(request, response, res);
      }
  
      public XMLGregorianCalendar convertDate(Date date) {
          if (date == null) {
              return null;
          }
  
          GregorianCalendar c = new GregorianCalendar();
          c.setTime(date);
          return datatypeFactory.newXMLGregorianCalendar(c).normalize();
      }
  
      public XMLGregorianCalendar convertCalendar(Calendar calendar) {
          if (calendar == null) {
              return null;
          }
  
          return datatypeFactory.newXMLGregorianCalendar((GregorianCalendar)calendar).normalize();
      }
  }
  