1/*
2  Copyright (C) 2002-2005 SKYRIX Software AG
3
4  This file is part of SOPE.
5
6  SOPE is free software; you can redistribute it and/or modify it under
7  the terms of the GNU Lesser General Public License as published by the
8  Free Software Foundation; either version 2, or (at your option) any
9  later version.
10
11  SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
12  WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14  License for more details.
15
16  You should have received a copy of the GNU Lesser General Public
17  License along with SOPE; see the file COPYING.  If not, write to the
18  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19  02111-1307, USA.
20*/
21
22#include "SoProduct.h"
23#include "SoProductClassInfo.h"
24#include "SoProductResourceManager.h"
25#include "SoClassRegistry.h"
26#include "SoClassSecurityInfo.h"
27#include "SoObject.h"
28#include "SoSecurityManager.h"
29#include <NGObjWeb/WOApplication.h>
30#include <NGObjWeb/WOResourceManager.h>
31#include <NGObjWeb/WOResponse.h>
32#include "common.h"
33
34@interface SoProduct(Privates)
35- (void)registerClassesFromDictionary:(NSDictionary *)_classToInfo;
36- (void)registerCategoriesFromDictionary:(NSDictionary *)_classToInfo;
37@end
38
39@implementation SoProduct
40
41static int debugOn     = 1;
42static int regDebugOn  = 0;
43static int loadDebugOn = 0;
44
45+ (void)initialize {
46  static BOOL didInit = NO;
47  if (!didInit) {
48    NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
49    didInit = YES;
50    regDebugOn  = [ud boolForKey:@"SoDebugProductRegistry"] ? 1 : 0;
51    loadDebugOn = [ud boolForKey:@"SoDebugProductLoading"] ? 1 : 0;
52  }
53}
54
55- (id)initWithDictionary:(NSDictionary *)_dict {
56  if ((self = [super init])) {
57    self->requiredProducts = [[_dict objectForKey:@"requires"] copy];
58    self->publicResources  = [[_dict objectForKey:@"publicResources"] copy];
59
60    [self registerClassesFromDictionary:[_dict objectForKey:@"classes"]];
61    [self registerCategoriesFromDictionary:[_dict objectForKey:@"categories"]];
62  }
63  return self;
64}
65
66- (id)initWithBundle:(NSBundle *)_bundle {
67  NSString     *manifestPath;
68  NSDictionary *manifest;
69
70  if (_bundle == nil) {
71    [self release];
72    return nil;
73  }
74  self->bundle = [_bundle retain];
75  manifestPath = [self->bundle pathForResource:@"product" ofType:@"plist"];
76  if ([manifestPath length] == 0) {
77    [self release];
78    return nil;
79  }
80
81  manifest = [NSDictionary dictionaryWithContentsOfFile:manifestPath];
82  if (manifest == nil) {
83    [self logWithFormat:@"could not parse manifest: %@", manifestPath];
84    [self release];
85    return nil;
86  }
87
88  self->resourceManager =
89    [[SoProductResourceManager alloc] initWithProduct:self];
90  if (self->resourceManager == nil)
91    [self logWithFormat:@"failed to instantiate resourcemanager for bundle"];
92
93  return [self initWithDictionary:manifest];
94}
95
96- (void)dealloc {
97  [self->resourceManager detachFromContainer];
98
99  [self->resourceManager  release];
100  [self->publicResources  release];
101  [self->requiredProducts release];
102  [self->categories       release];
103  [self->classes          release];
104  [self->bundle           release];
105  [super dealloc];
106}
107
108/* accessors */
109
110- (NSArray *)requiredProducts {
111  return self->requiredProducts;
112}
113
114- (NSBundle *)bundle {
115  return self->bundle;
116}
117
118- (BOOL)isMainProduct {
119  if (self->bundle == nil) return YES;
120  if (self->bundle == [NSBundle mainBundle]) return YES;
121  return NO;
122}
123
124- (NSString *)productName {
125  if ([self isMainProduct])
126    return @"MAIN";
127
128  return [[[self->bundle bundlePath]
129	    lastPathComponent] stringByDeletingPathExtension];
130}
131
132- (BOOL)isPublicResource:(NSString *)_key {
133  return [self->publicResources containsObject:_key] ? YES : NO;
134}
135
136/* parsing manifest */
137
138- (void)registerCategoryNamed:(NSString *)_name info:(NSDictionary *)_info {
139  SoProductCategoryInfo *catInfo;
140
141  if (regDebugOn)
142    [self logWithFormat:@"  register category on '%@'", _name];
143
144  catInfo = [[SoProductCategoryInfo alloc]
145		initWithName:_name manifest:_info product:self];
146  if (catInfo == nil) {
147    [self logWithFormat:@"   could not init category info for '%@'", _name];
148    return;
149  }
150  if ([self->categories objectForKey:_name]) {
151    [self errorWithFormat:
152            @"duplicate declaration of category on '%@' in product.",
153            _name];
154    [catInfo release];
155    return;
156  }
157
158  if (self->categories == nil)
159    self->categories = [[NSMutableDictionary alloc] init];
160
161  [self->categories setObject:catInfo forKey:_name];
162  [catInfo autorelease];
163}
164
165- (void)registerClassNamed:(NSString *)_name info:(NSDictionary *)_info {
166  SoProductClassInfo *classInfo;
167
168  if (regDebugOn)
169    [self logWithFormat:@"  register class: %@", _name];
170
171  classInfo = [[SoProductClassInfo alloc]
172		initWithName:_name manifest:_info product:self];
173  if (classInfo == nil) {
174    [self debugWithFormat:@"   could not init class info for '%@'", _name];
175    return;
176  }
177  if ([self->classes objectForKey:_name]) {
178    [self errorWithFormat:@"duplicate declaration of class %@ in product "
179            @"(registering as category)",
180            _name];
181    [classInfo release];
182    [self registerCategoryNamed:_name info:_info];
183    return;
184  }
185
186  if (self->classes == nil)
187    self->classes = [[NSMutableDictionary alloc] init];
188
189  [self->classes setObject:classInfo forKey:_name];
190  [classInfo autorelease];
191}
192
193- (void)registerClassesFromDictionary:(NSDictionary *)_classToInfo {
194  NSEnumerator *names;
195  NSMutableSet *regClasses;
196  NSString *className;
197
198  regClasses = [NSMutableSet setWithCapacity:16];
199  names = [_classToInfo keyEnumerator];
200  while ((className = [names nextObject])) {
201    NSDictionary *info;
202
203    if ([regClasses containsObject:className])
204      continue;
205
206    info = [_classToInfo objectForKey:className];
207    [self registerClassNamed:className info:info];
208    [regClasses addObject:className];
209  }
210}
211- (void)registerCategoriesFromDictionary:(NSDictionary *)_classToInfo {
212  NSEnumerator *names;
213  NSMutableSet *regCats;
214  NSString *className;
215
216  regCats = [NSMutableSet setWithCapacity:16];
217  names = [_classToInfo keyEnumerator];
218  while ((className = [names nextObject])) {
219    NSDictionary *info;
220
221    if ([regCats containsObject:className])
222      continue;
223
224    info = [_classToInfo objectForKey:className];
225    [self registerCategoryNamed:className info:info];
226    [regCats addObject:className];
227  }
228}
229
230/* loading */
231
232- (BOOL)load {
233  SoClassRegistry *registry;
234
235  if (self->flags.isLoaded) {
236    if (loadDebugOn)
237      [self logWithFormat:@"product already loaded: %@", self];
238    return YES;
239  }
240
241  if (loadDebugOn)
242    [self logWithFormat:@"loading product: %@", self];
243  self->flags.isLoaded = 1;
244
245  /* check whether bundle is binary ! */
246
247  if ((self->bundle != nil) && (self->bundle != [NSBundle mainBundle])) {
248    if (loadDebugOn) {
249      [self logWithFormat:@"  loading bundle of product: %@",
250	      [self->bundle bundlePath]];
251    }
252
253    if (![self->bundle load]) {
254      if (loadDebugOn) [self logWithFormat:@"  failed to load bundle."];
255      return NO;
256    }
257    self->flags.isCodeLoaded = 1;
258  }
259
260  registry = [SoClassRegistry sharedClassRegistry];
261
262  if (loadDebugOn) {
263    [self logWithFormat:@"  registering %i classes ...",
264	    [self->classes count]];
265  }
266
267  [[self->classes allValues]
268    makeObjectsPerformSelector:@selector(applyOnRegistry:)
269    withObject:registry];
270
271  if (loadDebugOn) {
272    [self logWithFormat:@"  registering %i categories ...",
273	    [self->categories count]];
274  }
275
276  [[self->categories allValues]
277    makeObjectsPerformSelector:@selector(applyOnRegistry:)
278    withObject:registry];
279
280  if (loadDebugOn)
281    [self logWithFormat:@"done loading product."];
282  return YES;
283}
284
285- (BOOL)reloadIfPossible {
286  /* only possible if no product ObjC code is loaded */
287  if (self->flags.isCodeLoaded) return NO;
288
289  return NO;
290}
291
292/* product as a SoObject */
293
294- (NSString *)baseURLInContext:(id)_ctx {
295  /* Note: cannot use -stringByAppendingPathComponent: on OSX ! */
296  NSString *baseURL, *cname;
297
298  baseURL = [self rootURLInContext:_ctx];
299  if (![baseURL hasSuffix:@"/"])
300    baseURL = [baseURL stringByAppendingString:@"/"];
301
302  baseURL = [baseURL stringByAppendingString:@"ControlPanel/Products/"];
303  cname   = [[self productName] stringByEscapingURL];
304  baseURL = [baseURL stringByAppendingString:cname];
305  return baseURL;
306}
307
308- (NSArray *)allKeys {
309  return [NSArray arrayWithObject:@"Resources"];
310}
311
312- (BOOL)hasName:(NSString *)_key inContext:(id)_ctx {
313  if ([_key isEqualToString:@"Resources"])
314    return YES;
315  return [super hasName:_key inContext:_ctx];
316}
317
318- (id)lookupName:(NSString *)_key inContext:(id)_ctx acquire:(BOOL)_flag {
319  if ([_key isEqualToString:@"Resources"])
320    return [self resourceManager];
321
322  return [super lookupName:_key inContext:_ctx acquire:_flag];
323}
324
325/* resource manager */
326
327- (WOResourceManager *)resourceManager {
328  if ([self isMainProduct])
329    return [[WOApplication application] resourceManager];
330
331  if (self->resourceManager == nil) {
332    [self warnWithFormat:@"resource-manager was nil ..."];
333    self->resourceManager =
334      [[SoProductResourceManager alloc] initWithProduct:self];
335  }
336  return self->resourceManager;
337}
338
339/* HTML representation */
340
341- (void)appendToResponse:(WOResponse *)_response inContext:(id)_ctx {
342  [_response appendContentString:@"<h3>SOPE Product: "];
343  [_response appendContentHTMLString:[self productName]];
344  [_response appendContentString:@"</h3>"];
345
346  [_response appendContentString:
347	       @"<li><a href=\"Resources/\">Resources</a></li>"];
348}
349
350/* debugging */
351
352- (NSString *)loggingPrefix {
353  return [NSString stringWithFormat:@"[so-product:%@]",
354                     [[[self bundle] bundlePath] lastPathComponent]];
355}
356- (BOOL)isDebuggingEnabled {
357  return debugOn ? YES : NO;
358}
359
360/* description */
361
362- (NSString *)description {
363  NSMutableString *ms;
364  unsigned cnt;
365
366  ms = [NSMutableString stringWithCapacity:64];
367  [ms appendFormat:@"<0x%p[%@]:", self, NSStringFromClass([self class])];
368
369  if (self->flags.isLoaded)
370    [ms appendFormat:@" loaded"];
371  if (self->flags.isCodeLoaded)
372    [ms appendFormat:@" code-loaded"];
373
374  if (self->bundle)
375    [ms appendFormat:@" bundle=%@", [self->bundle bundlePath]];
376
377  if ((cnt = [self->classes count]) > 0)
378    [ms appendFormat:@" #classes=%d", cnt];
379  if ((cnt = [self->categories count]) > 0)
380    [ms appendFormat:@" #categories=%d", cnt];
381  if ((cnt = [self->publicResources count]) > 0)
382    [ms appendFormat:@" #pubrsrc=%d", cnt];
383
384  if (self->resourceManager)
385    [ms appendFormat:@" rm=0x%p", self->resourceManager];
386
387  [ms appendString:@">"];
388  return ms;
389}
390
391@end /* SoProduct */
392