]> git.argeo.org Git - lgpl/argeo-commons.git/blob - jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/LinkServlet.java
62cdc5f6b08d92ce58fdc60e1d76193cbed9100d
[lgpl/argeo-commons.git] / jcr / org.argeo.cms.jcr / src / org / argeo / cms / jcr / internal / servlet / LinkServlet.java
1 package org.argeo.cms.jcr.internal.servlet;
2
3 import static javax.jcr.Property.JCR_DESCRIPTION;
4 import static javax.jcr.Property.JCR_LAST_MODIFIED;
5 import static javax.jcr.Property.JCR_TITLE;
6
7 import java.io.IOException;
8 import java.io.PrintWriter;
9 import java.net.MalformedURLException;
10 import java.net.URL;
11 import java.security.PrivilegedExceptionAction;
12 import java.util.Calendar;
13 import java.util.Collection;
14
15 import javax.jcr.Node;
16 import javax.jcr.NodeIterator;
17 import javax.jcr.Repository;
18 import javax.jcr.RepositoryException;
19 import javax.jcr.Session;
20 import javax.security.auth.Subject;
21 import javax.security.auth.login.LoginContext;
22 import javax.security.auth.login.LoginException;
23 import javax.servlet.ServletException;
24 import javax.servlet.http.HttpServlet;
25 import javax.servlet.http.HttpServletRequest;
26 import javax.servlet.http.HttpServletResponse;
27
28 import org.argeo.api.cms.CmsAuth;
29 import org.argeo.api.cms.CmsConstants;
30 import org.argeo.cms.CmsException;
31 import org.argeo.cms.jcr.CmsJcrUtils;
32 import org.argeo.jcr.JcrUtils;
33 import org.osgi.framework.BundleContext;
34 import org.osgi.framework.FrameworkUtil;
35 import org.osgi.framework.ServiceReference;
36
37 public class LinkServlet extends HttpServlet {
38 private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
39
40 private static final long serialVersionUID = 3749990143146845708L;
41
42 @Override
43 protected void service(HttpServletRequest request, HttpServletResponse response)
44 throws ServletException, IOException {
45 String path = request.getPathInfo();
46 String userAgent = request.getHeader("User-Agent").toLowerCase();
47 boolean isBot = false;
48 // boolean isCompatibleBrowser = false;
49 if (userAgent.contains("bot") || userAgent.contains("facebook") || userAgent.contains("twitter")) {
50 isBot = true;
51 }
52 // else if (userAgent.contains("webkit") ||
53 // userAgent.contains("gecko") || userAgent.contains("firefox")
54 // || userAgent.contains("msie") || userAgent.contains("chrome") ||
55 // userAgent.contains("chromium")
56 // || userAgent.contains("opera") || userAgent.contains("browser"))
57 // {
58 // isCompatibleBrowser = true;
59 // }
60
61 if (isBot) {
62 // log.warn("# BOT " + request.getHeader("User-Agent"));
63 canonicalAnswer(request, response, path);
64 return;
65 }
66
67 // if (isCompatibleBrowser && log.isTraceEnabled())
68 // log.trace("# BWS " + request.getHeader("User-Agent"));
69 redirectTo(response, "/#" + path);
70 }
71
72 private void redirectTo(HttpServletResponse response, String location) {
73 response.setHeader("Location", location);
74 response.setStatus(HttpServletResponse.SC_FOUND);
75 }
76
77 // private boolean canonicalAnswerNeededBy(HttpServletRequest request) {
78 // String userAgent = request.getHeader("User-Agent").toLowerCase();
79 // return userAgent.startsWith("facebookexternalhit/");
80 // }
81
82 /** For bots which don't understand RWT. */
83 private void canonicalAnswer(HttpServletRequest request, HttpServletResponse response, String path) {
84 Session session = null;
85 try {
86 PrintWriter writer = response.getWriter();
87 session = Subject.doAs(anonymousLogin(), new PrivilegedExceptionAction<Session>() {
88
89 @Override
90 public Session run() throws Exception {
91 Collection<ServiceReference<Repository>> srs = bc.getServiceReferences(Repository.class,
92 "(" + CmsConstants.CN + "=" + CmsConstants.EGO_REPOSITORY + ")");
93 Repository repository = bc.getService(srs.iterator().next());
94 return repository.login();
95 }
96
97 });
98 Node node = session.getNode(path);
99 String title = node.hasProperty(JCR_TITLE) ? node.getProperty(JCR_TITLE).getString() : node.getName();
100 String desc = node.hasProperty(JCR_DESCRIPTION) ? node.getProperty(JCR_DESCRIPTION).getString() : null;
101 Calendar lastUpdate = node.hasProperty(JCR_LAST_MODIFIED) ? node.getProperty(JCR_LAST_MODIFIED).getDate()
102 : null;
103 String url = getCanonicalUrl(node, request);
104 String imgUrl = null;
105 // TODO support images
106 // loop: for (NodeIterator it = node.getNodes(); it.hasNext();) {
107 // // Takes the first found cms:image
108 // Node child = it.nextNode();
109 // if (child.isNodeType(CMS_IMAGE)) {
110 // imgUrl = getDataUrl(child, request);
111 // break loop;
112 // }
113 // }
114 StringBuilder buf = new StringBuilder();
115 buf.append("<html>");
116 buf.append("<head>");
117 writeMeta(buf, "og:title", escapeHTML(title));
118 writeMeta(buf, "og:type", "website");
119 buf.append("<meta name='twitter:card' content='summary' />");
120 buf.append("<meta name='twitter:site' content='@argeo_org' />");
121 writeMeta(buf, "og:url", url);
122 if (desc != null)
123 writeMeta(buf, "og:description", escapeHTML(desc));
124 if (imgUrl != null)
125 writeMeta(buf, "og:image", imgUrl);
126 if (lastUpdate != null)
127 writeMeta(buf, "og:updated_time", Long.toString(lastUpdate.getTime().getTime()));
128 buf.append("</head>");
129 buf.append("<body>");
130 buf.append("<p><b>!! This page is meant for indexing robots, not for real people," + " visit <a href='/#")
131 .append(path).append("'>").append(escapeHTML(title)).append("</a> instead.</b></p>");
132 writeCanonical(buf, node);
133 buf.append("</body>");
134 buf.append("</html>");
135 writer.print(buf.toString());
136
137 response.setHeader("Content-Type", "text/html");
138 writer.flush();
139 } catch (Exception e) {
140 throw new CmsException("Cannot write canonical answer", e);
141 } finally {
142 JcrUtils.logoutQuietly(session);
143 }
144 }
145
146 /**
147 * From http://stackoverflow.com/questions/1265282/recommended-method-for-
148 * escaping-html-in-java (+ escaping '). TODO Use
149 * org.apache.commons.lang.StringEscapeUtils
150 */
151 private String escapeHTML(String s) {
152 StringBuilder out = new StringBuilder(Math.max(16, s.length()));
153 for (int i = 0; i < s.length(); i++) {
154 char c = s.charAt(i);
155 if (c > 127 || c == '\'' || c == '"' || c == '<' || c == '>' || c == '&') {
156 out.append("&#");
157 out.append((int) c);
158 out.append(';');
159 } else {
160 out.append(c);
161 }
162 }
163 return out.toString();
164 }
165
166 private void writeMeta(StringBuilder buf, String tag, String value) {
167 buf.append("<meta property='").append(tag).append("' content='").append(value).append("'/>");
168 }
169
170 private void writeCanonical(StringBuilder buf, Node node) throws RepositoryException {
171 buf.append("<div>");
172 if (node.hasProperty(JCR_TITLE))
173 buf.append("<p>").append(node.getProperty(JCR_TITLE).getString()).append("</p>");
174 if (node.hasProperty(JCR_DESCRIPTION))
175 buf.append("<p>").append(node.getProperty(JCR_DESCRIPTION).getString()).append("</p>");
176 NodeIterator children = node.getNodes();
177 while (children.hasNext()) {
178 writeCanonical(buf, children.nextNode());
179 }
180 buf.append("</div>");
181 }
182
183 // DATA
184 private StringBuilder getServerBaseUrl(HttpServletRequest request) {
185 try {
186 URL url = new URL(request.getRequestURL().toString());
187 StringBuilder buf = new StringBuilder();
188 buf.append(url.getProtocol()).append("://").append(url.getHost());
189 if (url.getPort() != -1)
190 buf.append(':').append(url.getPort());
191 return buf;
192 } catch (MalformedURLException e) {
193 throw new CmsException("Cannot extract server base URL from " + request.getRequestURL(), e);
194 }
195 }
196
197 private String getDataUrl(Node node, HttpServletRequest request) throws RepositoryException {
198 try {
199 StringBuilder buf = getServerBaseUrl(request);
200 buf.append(CmsJcrUtils.getDataPath(CmsConstants.EGO_REPOSITORY, node));
201 return new URL(buf.toString()).toString();
202 } catch (MalformedURLException e) {
203 throw new CmsException("Cannot build data URL for " + node, e);
204 }
205 }
206
207 // public static String getDataPath(Node node) throws
208 // RepositoryException {
209 // assert node != null;
210 // String userId = node.getSession().getUserID();
211 //// if (log.isTraceEnabled())
212 //// log.trace(userId + " : " + node.getPath());
213 // StringBuilder buf = new StringBuilder();
214 // boolean isAnonymous =
215 // userId.equalsIgnoreCase(NodeConstants.ROLE_ANONYMOUS);
216 // if (isAnonymous)
217 // buf.append(WEBDAV_PUBLIC);
218 // else
219 // buf.append(WEBDAV_PRIVATE);
220 // Session session = node.getSession();
221 // Repository repository = session.getRepository();
222 // String cn;
223 // if (repository.isSingleValueDescriptor(NodeConstants.CN)) {
224 // cn = repository.getDescriptor(NodeConstants.CN);
225 // } else {
226 //// log.warn("No cn defined in repository, using " +
227 // NodeConstants.NODE);
228 // cn = NodeConstants.NODE;
229 // }
230 // return
231 // buf.append('/').append(cn).append('/').append(session.getWorkspace().getName()).append(node.getPath())
232 // .toString();
233 // }
234
235 private String getCanonicalUrl(Node node, HttpServletRequest request) throws RepositoryException {
236 try {
237 StringBuilder buf = getServerBaseUrl(request);
238 buf.append('/').append('!').append(node.getPath());
239 return new URL(buf.toString()).toString();
240 } catch (MalformedURLException e) {
241 throw new CmsException("Cannot build data URL for " + node, e);
242 }
243 // return request.getRequestURL().append('!').append(node.getPath())
244 // .toString();
245 }
246
247 private Subject anonymousLogin() {
248 Subject subject = new Subject();
249 LoginContext lc;
250 try {
251 lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_ANONYMOUS, subject);
252 lc.login();
253 return subject;
254 } catch (LoginException e) {
255 throw new CmsException("Cannot login as anonymous", e);
256 }
257 }
258
259 }