View Javadoc
1   /*
2    * This file is part of ***  M y C o R e  ***
3    * See http://www.mycore.de/ for details.
4    *
5    * MyCoRe is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    *
10   * MyCoRe is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with MyCoRe.  If not, see <http://www.gnu.org/licenses/>.
17   */
18  
19  package org.mycore.wcms2.navigation;
20  
21  import java.io.IOException;
22  import java.io.OutputStream;
23  import java.net.MalformedURLException;
24  import java.net.URL;
25  import java.nio.file.Files;
26  import java.nio.file.Paths;
27  import java.util.Optional;
28  
29  import org.apache.logging.log4j.LogManager;
30  import org.apache.logging.log4j.Logger;
31  import org.jdom2.Document;
32  import org.jdom2.Element;
33  import org.jdom2.JDOMException;
34  import org.jdom2.input.SAXBuilder;
35  import org.jdom2.output.Format;
36  import org.jdom2.output.XMLOutputter;
37  import org.mycore.common.config.MCRConfiguration2;
38  import org.mycore.common.content.MCRURLContent;
39  import org.mycore.tools.MyCoReWebPageProvider;
40  import org.mycore.wcms2.MCRWebPagesSynchronizer;
41  import org.xml.sax.SAXException;
42  
43  import com.google.gson.JsonArray;
44  import com.google.gson.JsonElement;
45  import com.google.gson.JsonObject;
46  
47  import jakarta.ws.rs.WebApplicationException;
48  import jakarta.ws.rs.core.MediaType;
49  import jakarta.ws.rs.core.Response;
50  import jakarta.ws.rs.core.Response.Status;
51  
52  /**
53   * This class provides methods to get, create and save MyCoRe webpages.
54   *
55   * @author Matthias Eichner
56   */
57  public class MCRWCMSContentManager {
58  
59      private static final Logger LOGGER = LogManager.getLogger(MCRWCMSContentManager.class);
60  
61      private MCRWCMSSectionProvider sectionProvider;
62  
63      public enum ErrorType {
64          notExist, invalidFile, notMyCoReWebPage, invalidDirectory, couldNotSave, couldNotMove
65      }
66  
67      public MCRWCMSContentManager() {
68          this.sectionProvider = MCRConfiguration2.<MCRWCMSSectionProvider>getInstanceOf("MCR.WCMS2.sectionProvider")
69              .orElseGet(MCRWCMSDefaultSectionProvider::new);
70      }
71  
72      /**
73       * Return a json object with the content of a MyCoRe webpage.
74       * <p>
75       * {
76       *  type: "content",
77       *  content: @see {@link MCRWCMSDefaultSectionProvider}
78       * }
79       * </p>
80       * <p>
81       * If an error occur (e.g. file not exist) the returning json
82       * looks like:<br>
83       * {
84       *  type: "error",
85       *  errorType: "invalidFile"
86       *  webpageId: "myfolder/webpage1.xml"
87       * }
88       * </p>
89       *
90       * @param webpageId id of the webpage
91       * @return json object
92       * @see ErrorType
93       */
94      public JsonObject getContent(String webpageId) throws IOException, JDOMException, SAXException {
95          boolean isXML = webpageId.endsWith(".xml");
96          URL resourceURL = null;
97          try {
98              resourceURL = MCRWebPagesSynchronizer.getURL(webpageId);
99          } catch (MalformedURLException e) {
100             throwError(ErrorType.invalidDirectory, webpageId);
101         }
102         // file is not in web application directory
103         if (!isXML) {
104             throwError(ErrorType.invalidFile, webpageId);
105         }
106         Document doc = null;
107         if (resourceURL == null) {
108             MyCoReWebPageProvider wpp = new MyCoReWebPageProvider();
109             wpp.addSection("neuer Eintrag", new Element("p").setText("TODO"), "de");
110             doc = wpp.getXML();
111         } else {
112             doc = new MCRURLContent(resourceURL).asXML();
113         }
114         Element rootElement = doc.getRootElement();
115         if (!"MyCoReWebPage".equals(rootElement.getName())) {
116             throwError(ErrorType.notMyCoReWebPage, webpageId);
117         }
118         // return content
119         return getContent(rootElement);
120     }
121 
122     public JsonObject getContent(Element e) {
123         JsonArray sectionArray = this.sectionProvider.toJSON(e);
124         JsonObject content = new JsonObject();
125         content.addProperty("type", "content");
126         content.add("content", sectionArray);
127         return content;
128     }
129 
130     /**
131      * Saves the content of the given items.
132      * 
133      * Returns a json object, if everything is ok:
134      * <p>
135      * { type: "saveDone" }
136      * </p>
137      * 
138      * <p>
139      * If one or more files could'nt be saved because of an error the returning
140      * json looks like:<br>
141      * {
142      *   type: "error",
143      *   errorArray: [
144      *       {type: "error", errorType: "invalidDirectory", webpageId: "abc.xml"},
145      *       {type: "error", errorType: "invalidFile", webpageId: "abc2.xml"}
146      *       ...
147      *  ]
148      * }
149      * </p>
150      * 
151      */
152     public void save(JsonArray items) {
153         XMLOutputter out = new XMLOutputter(Format.getRawFormat().setEncoding("UTF-8"));
154         for (JsonElement e : items) {
155             validateContent(e).ifPresent(item -> {
156                 String webpageId = getWebPageId(item).get();
157                 if (!webpageId.endsWith(".xml")) {
158                     throwError(ErrorType.invalidFile, webpageId);
159                 }
160                 JsonArray content = item.get("content").getAsJsonArray();
161                 Element mycoreWebpage = this.sectionProvider.fromJSON(content);
162                 // save
163                 try (OutputStream fout = MCRWebPagesSynchronizer.getOutputStream(webpageId)) {
164                     out.output(new Document(mycoreWebpage), fout);
165                 } catch (IOException | RuntimeException exc) {
166                     LOGGER.error("Error while saving {}", webpageId, exc);
167                     throwError(ErrorType.couldNotSave, webpageId);
168                 }
169             });
170         }
171     }
172 
173     private Optional<String> getWebPageId(JsonObject item) {
174         JsonElement webpageIdElement = item.has("href") ? item.get("href")
175             : (item.has("hrefStartingPage") ? item
176                 .get("hrefStartingPage") : null);
177         if (webpageIdElement == null || !webpageIdElement.isJsonPrimitive()) {
178             return Optional.empty();
179         }
180         return Optional.of(webpageIdElement)
181             .map(JsonElement::getAsString);
182     }
183 
184     private Optional<JsonObject> validateContent(JsonElement e) {
185         if (!e.isJsonObject()) {
186             LOGGER.warn("Invalid json element in items {}", e);
187             return Optional.empty();
188         }
189         JsonObject item = e.getAsJsonObject();
190         if (!item.has("dirty") || !item.get("dirty").getAsBoolean()) {
191             return Optional.empty();
192         }
193         //TODO wenn man nur den href ändert und nicht den content muss die datei
194         // trotzdem umgeschrieben werden -> check auf file exists
195         if (!getWebPageId(item).isPresent() || !item.has("content") || !item.get("content").isJsonArray()) {
196             return Optional.empty();
197         }
198         return Optional.of(item);
199     }
200 
201     public void move(String from, String to) {
202         try {
203             // get from
204             URL fromURL = MCRWebPagesSynchronizer.getURL(from);
205             Document document;
206             if (fromURL == null) {
207                 // if the from resource couldn't be found we assume its not created yet.
208                 MyCoReWebPageProvider wpp = new MyCoReWebPageProvider();
209                 wpp.addSection("neuer Eintrag", new Element("p").setText("TODO"), "de");
210                 document = wpp.getXML();
211             } else {
212                 SAXBuilder builder = new SAXBuilder();
213                 document = builder.build(fromURL);
214             }
215             // save
216             XMLOutputter out = new XMLOutputter(Format.getPrettyFormat().setEncoding("UTF-8"));
217             try (OutputStream fout = MCRWebPagesSynchronizer.getOutputStream(to)) {
218                 out.output(document, fout);
219             }
220             // delete old
221             if (fromURL != null) {
222                 Files.delete(Paths.get(fromURL.toURI()));
223             }
224         } catch (Exception exc) {
225             LOGGER.error("Error moving {} to {}", from, to, exc);
226             throwError(ErrorType.couldNotMove, to);
227         }
228     }
229 
230     /**
231      * Throws a new webapplication exception with given error type.
232      * <code>{ type: "error", errorType: "typeOfError", webpageId: "abc.xml" }</code>
233      * 
234      * @param errorType type of the error
235      * @param webpageId webpageId where the error occur
236      */
237     private void throwError(ErrorType errorType, String webpageId) {
238         JsonObject error = new JsonObject();
239         error.addProperty("type", errorType.name());
240         error.addProperty("webpageId", webpageId);
241         Response response = Response.status(Status.INTERNAL_SERVER_ERROR).entity(error.toString())
242             .type(MediaType.APPLICATION_JSON).build();
243         throw new WebApplicationException(response);
244     }
245 
246 }