001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018: package org.apache.lenya.cms.publication;
019:
020: import java.io.File;
021: import java.util.ArrayList;
022: import java.util.Arrays;
023: import java.util.HashMap;
024: import java.util.List;
025: import java.util.Map;
026:
027: import org.apache.avalon.framework.configuration.Configuration;
028: import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
029: import org.apache.avalon.framework.logger.AbstractLogEnabled;
030: import org.apache.lenya.cms.repository.Node;
031: import org.apache.lenya.cms.repository.Session;
032:
033: /**
034: * A publication's configuration. Keep in sync with
035: * src/resources/build/publication.rng!
036: */
037: public class PublicationConfiguration extends AbstractLogEnabled
038: implements Publication {
039:
040: private String id;
041: private String name;
042: private File servletContext;
043: private DocumentIdToPathMapper mapper = null;
044: private ArrayList languages = new ArrayList();
045: private String defaultLanguage = null;
046: private String breadcrumbprefix = null;
047: private String instantiatorHint = null;
048: private String contentDir = null;
049:
050: private boolean isConfigLoaded = false;
051:
052: /**
053: * <code>CONFIGURATION_FILE</code> The publication configuration file
054: */
055: public static final String CONFIGURATION_FILE = CONFIGURATION_PATH
056: + File.separator + "publication.xml";
057:
058: private static final String CONFIGURATION_NAMESPACE = "http://apache.org/cocoon/lenya/publication/1.1";
059:
060: // properties marked with "*" are currently not parsed by this class.
061: private static final String ELEMENT_NAME = "name";
062: private static final String ELEMENT_DESCRIPTION = "description"; // *
063: private static final String ELEMENT_LONGDESC = "longdesc"; // *
064: private static final String ELEMENT_VERSION = "version"; // *
065: private static final String ELEMENT_LENYA_VERSION = "lenya-version"; // *
066: private static final String ELEMENT_LENYA_REVISION = "lenya-revision"; // *
067: private static final String ELEMENT_COCOON_VERSION = "cocoon-version"; // *
068: private static final String ELEMENT_LANGUAGES = "languages";
069: private static final String ELEMENT_LANGUAGE = "language";
070: private static final String ATTRIBUTE_DEFAULT_LANGUAGE = "default";
071: private static final String ELEMENT_TEMPLATE = "template";
072: private static final String ATTRIBUTE_ID = "id";
073: private static final String ELEMENT_TEMPLATE_INSTANTIATOR = "template-instantiator";
074: private static final String ATTRIBUTE_NAME = "name";
075: private static final String ELEMENT_PATH_MAPPER = "path-mapper";
076: private static final String ELEMENT_DOCUMENT_BUILDER = "document-builder";
077: private static final String ELEMENT_SITE_MANAGER = "site-manager";
078: private static final String ELEMENT_RESOURCE_TYPES = "resource-types";// *
079: private static final String ELEMENT_RESOURCE_TYPE = "resource-type";// *
080: private static final String ATTRIBUTE_WORKFLOW = "workflow";
081: private static final String ELEMENT_MODULES = "modules";// *
082: private static final String ELEMENT_MODULE = "module";// *
083: private static final String ELEMENT_BREADCRUMB_PREFIX = "breadcrumb-prefix";
084: private static final String ELEMENT_CONTENT_DIR = "content-dir";
085: private static final String ATTRIBUTE_SRC = "src";
086: private static final String ELEMENT_PROXIES = "proxies";
087: private static final String ELEMENT_PROXY = "proxy";
088: private static final String ATTRIBUTE_AREA = "area";
089: private static final String ATTRIBUTE_URL = "url";
090: private static final String ATTRIBUTE_SSL = "ssl";
091:
092: /**
093: * Creates a new instance of Publication
094: * @param _id the publication id
095: * @param servletContextPath the servlet context of this publication
096: * @throws PublicationException if there was a problem reading the config
097: * file
098: */
099: protected PublicationConfiguration(String _id,
100: String servletContextPath) throws PublicationException {
101: this .id = _id;
102: this .servletContext = new File(servletContextPath);
103: }
104:
105: /**
106: * Loads the configuration.
107: */
108: protected void loadConfiguration() {
109:
110: if (isConfigLoaded) {
111: return;
112: }
113:
114: if (getLogger().isDebugEnabled()) {
115: getLogger().debug(
116: "Loading configuration for publication [" + getId()
117: + "]");
118: }
119:
120: File configFile = getConfigurationFile();
121:
122: if (!configFile.exists()) {
123: getLogger().error(
124: "Config file [" + configFile.getAbsolutePath()
125: + "] does not exist: ",
126: new RuntimeException());
127: throw new RuntimeException("The configuration file ["
128: + configFile + "] does not exist!");
129: } else {
130: getLogger().debug(
131: "Configuration file [" + configFile + "] exists.");
132: }
133:
134: final boolean ENABLE_XML_NAMESPACES = true;
135: DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder(
136: ENABLE_XML_NAMESPACES);
137:
138: Configuration config;
139:
140: String pathMapperClassName = null;
141:
142: try {
143: config = builder.buildFromFile(configFile);
144:
145: this .name = config.getChild(ELEMENT_NAME).getValue();
146:
147: try {
148: // one sanity check for the proper namespace. we should really
149: // do that for every element,
150: // but since ELEMENT_PATH_MAPPER is mandatory, this should catch
151: // most cases of forgotten namespace.
152: if (config.getChild(ELEMENT_PATH_MAPPER).getNamespace() != CONFIGURATION_NAMESPACE) {
153: getLogger()
154: .warn(
155: "Deprecated configuration: the publication configuration elements in "
156: + configFile
157: + " must be in the "
158: + CONFIGURATION_NAMESPACE
159: + " namespace."
160: + " See webapp/lenya/resources/schemas/publication.xml.");
161: }
162: pathMapperClassName = config.getChild(
163: ELEMENT_PATH_MAPPER).getValue();
164: Class pathMapperClass = Class
165: .forName(pathMapperClassName);
166: this .mapper = (DocumentIdToPathMapper) pathMapperClass
167: .newInstance();
168: } catch (final ClassNotFoundException e) {
169: throw new PublicationException(
170: "Cannot instantiate documentToPathMapper: ["
171: + pathMapperClassName + "]", e);
172: }
173:
174: Configuration documentBuilderConfiguration = config
175: .getChild(ELEMENT_DOCUMENT_BUILDER, false);
176: if (documentBuilderConfiguration != null) {
177: this .documentBuilderHint = documentBuilderConfiguration
178: .getAttribute(ATTRIBUTE_NAME);
179: }
180:
181: Configuration[] _languages = config.getChild(
182: ELEMENT_LANGUAGES).getChildren(ELEMENT_LANGUAGE);
183: for (int i = 0; i < _languages.length; i++) {
184: Configuration languageConfig = _languages[i];
185: String language = languageConfig.getValue();
186: this .languages.add(language);
187: if (languageConfig.getAttribute(
188: ATTRIBUTE_DEFAULT_LANGUAGE, null) != null) {
189: this .defaultLanguage = language;
190: }
191: }
192:
193: Configuration siteManagerConfiguration = config.getChild(
194: ELEMENT_SITE_MANAGER, false);
195: if (siteManagerConfiguration != null) {
196: this .siteManagerName = siteManagerConfiguration
197: .getAttribute(ATTRIBUTE_NAME);
198: }
199:
200: Configuration proxiesConfig = config
201: .getChild(ELEMENT_PROXIES);
202: Configuration[] proxyConfigs = proxiesConfig
203: .getChildren(ELEMENT_PROXY);
204: for (int i = 0; i < proxyConfigs.length; i++) {
205: String url = proxyConfigs[i]
206: .getAttribute(ATTRIBUTE_URL);
207: String ssl = proxyConfigs[i]
208: .getAttribute(ATTRIBUTE_SSL);
209: String area = proxyConfigs[i]
210: .getAttribute(ATTRIBUTE_AREA);
211:
212: Proxy proxy = new Proxy();
213: proxy.setUrl(url);
214:
215: Object key = getProxyKey(area, Boolean.valueOf(ssl)
216: .booleanValue());
217: this .areaSsl2proxy.put(key, proxy);
218: if (getLogger().isDebugEnabled()) {
219: getLogger().debug(
220: "Adding proxy: [" + proxy + "] for area=["
221: + area + "] SSL=[" + ssl + "]");
222: }
223: }
224:
225: Configuration templateConfig = config.getChild(
226: ELEMENT_TEMPLATE, false);
227: // FIXME: this is a hack. For some reason, the old code seems to
228: // imply that a publication
229: // can have multiple templates. This is not the case. All this code
230: // should use a simple string
231: // rather than arrays at some point. For now, the old array is kept,
232: // to avoid having to deal
233: // with all kinds of NPEs that keep cropping up...
234: if (templateConfig == null) {
235: this .templates = new String[0]; // ugh. empty array to keep the
236: // legacy code from breaking.
237: } else {
238: this .templates = new String[1];
239: this .templates[0] = templateConfig
240: .getAttribute(ATTRIBUTE_ID);
241: }
242:
243: Configuration templateInstantiatorConfig = config.getChild(
244: ELEMENT_TEMPLATE_INSTANTIATOR, false);
245: if (templateInstantiatorConfig != null) {
246: this .instantiatorHint = templateInstantiatorConfig
247: .getAttribute(PublicationConfiguration.ATTRIBUTE_NAME);
248: }
249:
250: Configuration contentDirConfig = config.getChild(
251: ELEMENT_CONTENT_DIR, false);
252: if (contentDirConfig != null) {
253: this .contentDir = contentDirConfig
254: .getAttribute(ATTRIBUTE_SRC);
255: getLogger().info(
256: "Content directory loaded from pub configuration: "
257: + this .contentDir);
258: } else {
259: getLogger()
260: .info(
261: "No content directory specified within pub configuration!");
262: }
263:
264: Configuration resourceTypeConfig = config
265: .getChild(ELEMENT_RESOURCE_TYPES);
266: if (resourceTypeConfig != null) {
267: Configuration[] resourceTypeConfigs = resourceTypeConfig
268: .getChildren(ELEMENT_RESOURCE_TYPE);
269: for (int i = 0; i < resourceTypeConfigs.length; i++) {
270: String name = resourceTypeConfigs[i]
271: .getAttribute(ATTRIBUTE_NAME);
272: this .resourceTypes.add(name);
273:
274: String workflow = resourceTypeConfigs[i]
275: .getAttribute(ATTRIBUTE_WORKFLOW, null);
276: if (workflow != null) {
277: this .resourceType2workflow.put(name, workflow);
278: }
279: }
280: }
281:
282: } catch (final Exception e) {
283: throw new RuntimeException("Problem with config file: "
284: + configFile.getAbsolutePath(), e);
285: }
286:
287: this .breadcrumbprefix = config.getChild(
288: ELEMENT_BREADCRUMB_PREFIX).getValue("");
289:
290: isConfigLoaded = true;
291: }
292:
293: /**
294: * @return The configuration file ({@link #CONFIGURATION_FILE}).
295: */
296: protected File getConfigurationFile() {
297: File configFile = new File(getDirectory(), CONFIGURATION_FILE);
298: return configFile;
299: }
300:
301: /**
302: * Returns the publication ID.
303: * @return A string value.
304: */
305: public String getId() {
306: return this .id;
307: }
308:
309: /**
310: * Returns the servlet context this publication belongs to (usually, the
311: * <code>webapps/lenya</code> directory).
312: * @return A <code>File</code> object.
313: */
314: public File getServletContext() {
315: return this .servletContext;
316: }
317:
318: /**
319: * Returns the publication directory.
320: * @return A <code>File</code> object.
321: */
322: public File getDirectory() {
323: return new File(getServletContext(), PUBLICATION_PREFIX
324: + File.separator + getId());
325: }
326:
327: /**
328: * @see org.apache.lenya.cms.publication.Publication#getContentDirectory(String)
329: */
330: public File getContentDirectory(String area) {
331: return new File(getContentDir(), area);
332: }
333:
334: /**
335: * Set the path mapper
336: * @param _mapper The path mapper
337: */
338: public void setPathMapper(DefaultDocumentIdToPathMapper _mapper) {
339: assert _mapper != null;
340: this .mapper = _mapper;
341: }
342:
343: /**
344: * Returns the path mapper.
345: * @return a <code>DocumentIdToPathMapper</code>
346: */
347: public DocumentIdToPathMapper getPathMapper() {
348: if (this .mapper == null) {
349: loadConfiguration();
350: }
351: return this .mapper;
352: }
353:
354: /**
355: * Get the default language
356: * @return the default language
357: */
358: public String getDefaultLanguage() {
359: if (this .defaultLanguage == null) {
360: loadConfiguration();
361: }
362: return this .defaultLanguage;
363: }
364:
365: /**
366: * Set the default language
367: * @param language the default language
368: */
369: public void setDefaultLanguage(String language) {
370: this .defaultLanguage = language;
371: }
372:
373: /**
374: * Get all available languages for this publication
375: * @return an <code>Array</code> of languages
376: */
377: public String[] getLanguages() {
378: loadConfiguration();
379: return (String[]) this .languages
380: .toArray(new String[this .languages.size()]);
381: }
382:
383: /**
384: * Get the breadcrumb prefix. It can be used as a prefix if a publication is
385: * part of a larger site
386: * @return the breadcrumb prefix
387: */
388: public String getBreadcrumbPrefix() {
389: loadConfiguration();
390: return this .breadcrumbprefix;
391: }
392:
393: private String documentBuilderHint;
394:
395: /**
396: * Returns the document builder of this instance.
397: * @return A document builder.
398: */
399: public String getDocumentBuilderHint() {
400: loadConfiguration();
401: return this .documentBuilderHint;
402: }
403:
404: /**
405: * @see java.lang.Object#equals(java.lang.Object)
406: */
407: public boolean equals(Object object) {
408: boolean equals = false;
409:
410: if (getClass().isInstance(object)) {
411: Publication publication = (Publication) object;
412: equals = getId().equals(publication.getId())
413: && getServletContext().equals(
414: publication.getServletContext());
415: }
416:
417: return equals;
418: }
419:
420: /**
421: * @see java.lang.Object#hashCode()
422: */
423: public int hashCode() {
424: String key = getServletContext() + ":" + getId();
425: return key.hashCode();
426: }
427:
428: private Map areaSsl2proxy = new HashMap();
429:
430: /**
431: * Generates a hash key for a area-SSL combination.
432: * @param area The area.
433: * @param isSslProtected If the proxy is assigned for SSL-protected pages.
434: * @return An object.
435: */
436: protected Object getProxyKey(String area, boolean isSslProtected) {
437: return area + ":" + isSslProtected;
438: }
439:
440: /**
441: * @see org.apache.lenya.cms.publication.Publication#getProxy(org.apache.lenya.cms.publication.Document,
442: * boolean)
443: */
444: public Proxy getProxy(Document document, boolean isSslProtected) {
445: Proxy proxy = getProxy(document.getArea(), isSslProtected);
446:
447: if (getLogger().isDebugEnabled()) {
448: getLogger().debug(
449: "Resolving proxy for [" + document + "] SSL=["
450: + isSslProtected + "]");
451: getLogger().debug("Resolved proxy: [" + proxy + "]");
452: }
453:
454: return proxy;
455: }
456:
457: /**
458: * @see org.apache.lenya.cms.publication.Publication#getProxy(java.lang.String,
459: * boolean)
460: */
461: public Proxy getProxy(String area, boolean isSslProtected) {
462: loadConfiguration();
463: Object key = getProxyKey(area, isSslProtected);
464: Proxy proxy = (Proxy) this .areaSsl2proxy.get(key);
465: return proxy;
466: }
467:
468: /**
469: * @param area The area.
470: * @param isSslProtected If the proxy is for SSL-protected URLs.
471: * @param proxy The proxy to set.
472: */
473: protected void setProxy(String area, boolean isSslProtected,
474: Proxy proxy) {
475: Object key = getProxyKey(area, isSslProtected);
476: this .areaSsl2proxy.put(key, proxy);
477: }
478:
479: private String siteManagerName;
480:
481: /**
482: * @see org.apache.lenya.cms.publication.Publication#exists()
483: */
484: public boolean exists() {
485: return getConfigurationFile().exists();
486: }
487:
488: private String[] templates;
489:
490: /**
491: * @see org.apache.lenya.cms.publication.Publication#getTemplateIds()
492: */
493: public String[] getTemplateIds() {
494: loadConfiguration();
495: List list = Arrays.asList(this .templates);
496: return (String[]) list.toArray(new String[list.size()]);
497: }
498:
499: /**
500: * @see org.apache.lenya.cms.publication.Publication#getSiteManagerHint()
501: */
502: public String getSiteManagerHint() {
503: loadConfiguration();
504: return this .siteManagerName;
505: }
506:
507: /**
508: * @see org.apache.lenya.cms.publication.Publication#getInstantiatorHint()
509: */
510: public String getInstantiatorHint() {
511: loadConfiguration();
512: return this .instantiatorHint;
513: }
514:
515: /**
516: * @see org.apache.lenya.cms.publication.Publication#getContentDir()
517: */
518: public String getContentDir() {
519: loadConfiguration();
520: if (this .contentDir == null) {
521: this .contentDir = new File(getDirectory(), CONTENT_PATH)
522: .getAbsolutePath();
523: }
524: return this .contentDir;
525: }
526:
527: /**
528: * @see org.apache.lenya.cms.publication.Publication#getSourceURI()
529: */
530: public String getSourceURI() {
531: return Node.LENYA_PROTOCOL + PUBLICATION_PREFIX_URI + "/"
532: + this .id;
533: }
534:
535: /**
536: * @see org.apache.lenya.cms.publication.Publication#getContentURI(java.lang.String)
537: */
538: public String getContentURI(String area) {
539: return getSourceURI() + "/" + CONTENT_PATH + "/" + area;
540: }
541:
542: private Map resourceType2workflow = new HashMap();
543:
544: /**
545: * @see org.apache.lenya.cms.publication.Publication#getWorkflowSchema(org.apache.lenya.cms.publication.ResourceType)
546: */
547: public String getWorkflowSchema(ResourceType resourceType) {
548: String workflow = (String) this .resourceType2workflow
549: .get(resourceType.getName());
550: return workflow;
551: }
552:
553: private List resourceTypes = new ArrayList();
554:
555: /**
556: * @see org.apache.lenya.cms.publication.Publication#getResourceTypeNames()
557: */
558: public String[] getResourceTypeNames() {
559: loadConfiguration();
560: return (String[]) this .resourceTypes
561: .toArray(new String[this .resourceTypes.size()]);
562: }
563:
564: public String toString() {
565: return getId();
566: }
567:
568: public Area getArea(String name) throws PublicationException {
569: throw new IllegalStateException("Not implemented!");
570: }
571:
572: private String[] areas;
573:
574: public String[] getAreaNames() {
575: // TODO: make this more generic.
576: if (this .areas == null) {
577: List list = new ArrayList();
578: list.add(Publication.AUTHORING_AREA);
579: list.add(Publication.LIVE_AREA);
580: list.add(Publication.STAGING_AREA);
581: list.add(Publication.TRASH_AREA);
582: list.add(Publication.ARCHIVE_AREA);
583: this .areas = (String[]) list
584: .toArray(new String[list.size()]);
585: }
586: return this .areas;
587: }
588:
589: public DocumentFactory getFactory() {
590: throw new IllegalStateException("Not implemented!");
591: }
592:
593: public DocumentBuilder getDocumentBuilder() {
594: return null;
595: }
596:
597: public String getName() {
598: loadConfiguration();
599: return this .name;
600: }
601:
602: public Session getSession() {
603: throw new UnsupportedOperationException();
604: }
605:
606: }
|