incubator-alois-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From fla...@apache.org
Subject svn commit: r1031127 [9/22] - in /incubator/alois/trunk: ./ bin/ debian/ doc/ etc/ etc/alois/ etc/alois/apache2/ etc/alois/environments/ etc/alois/prisma/ etc/cron.d/ etc/default/ etc/logrotate.d/ prisma/ prisma/bin/ prisma/conf/ prisma/conf/prisma/ pr...
Date Thu, 04 Nov 2010 18:27:42 GMT
Added: incubator/alois/trunk/rails/app/models/chart.rb
URL: http://svn.apache.org/viewvc/incubator/alois/trunk/rails/app/models/chart.rb?rev=1031127&view=auto
==============================================================================
--- incubator/alois/trunk/rails/app/models/chart.rb (added)
+++ incubator/alois/trunk/rails/app/models/chart.rb Thu Nov  4 18:27:22 2010
@@ -0,0 +1,894 @@
+# Copyright 2010 The Apache Software Foundation.
+# 
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# 
+# http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+  class Chart < ActiveRecord::Base
+      has_and_belongs_to_many :report_templates
+
+    # Maximum length of values in source data and
+    # in charts. Values longer than this will be
+    # truncated.
+    MAX_VALUE_LENGTH = 50
+    # Amount of time source data of charts will be cached.
+    CACHING_TIMEOUT = 6 * 60 # minutes (6 hours)
+    # The different types of charts
+    CHART_TYPES = [:bar, :line, :pie]
+    # Descriptions of chart types
+    CHART_TEXTS = {
+      :bar =>  "Generates a bar chart. If you select a y-axis, the bar chart will be stacked with the the values of the y-axis as stacked pieces.",
+      :line => "Generates a line chart. Only the x-axis will take effect, no y-axis is allowed for this type of chart. In difference to the bar chart, the displayed range will not start from 0. The axis is adapted to the values returned from the query (min,max).",
+      :pie => "Generates a pie chart. Only the x-axis will take effect, no y-axis is allowed for this type of chart."
+      #TODO: :spider => "Spider Web chart"
+      
+    }
+    # Chart option names
+    CHART_OPTIONS = [:stacked, :flipped]   
+    # Default width of the chart in pixel
+    DEFAULT_WIDTH = 1000
+    # Default height of the chart in pixel
+    DEFAULT_HEIGHT = 600
+
+    attr_accessor :datasource
+    attr_accessor :zoom
+    attr_accessor :mode
+    
+    validates_presence_of :column1,:chart_type
+
+    # Validation of the chart
+    validate do |c|
+      ds = c.datasource
+      if ds	
+	c.errors.add("column1", "Not a valid column.") unless c.valid_column?("column1",ds, :check_type => true)
+	c.errors.add("column2", "Not a valid column.") unless c.valid_column?("column2",ds, :required => false)
+	c.errors.add("column3", "Not a valid column.") unless c.valid_column?("column3",ds, :required => false)
+	c.errors.add("aggregation_column", "Not a valid column.") unless 
+	  c.valid_column?("aggregation_column",ds, :required => false, :asterix_allowed => true)
+
+      end
+
+      c.errors.add("column3", "3 Dimensional pie chart not allowed") if 
+	c.column3 and c.chart_type == "pie"
+
+      begin
+	if c.time_range
+	  c.errors.add("time_range", "Datasource has no column 'data'.") if
+	    ds and not ds.table.columns_hash["date"]
+	  c.time_range.to_time_range if c.time_range
+	end
+      rescue
+	c.errors.add("time_range",$!)
+      end 
+    end
+
+    # alias for column1 (x-axis)
+    def category_column; column1; end
+    # alias for column2 (y-axis)
+    def serie_column; column2; end
+    # alias for column3 (z-axis)
+    def range_column; column3; end
+
+    # The time range of data that this chart is using.
+    def time_range
+      t = super
+      return nil if super == ""
+      t
+    end
+
+    # Return true if this type of chart can be
+    # rendered out of the given datasource
+    def applyable?(datasource)
+      c = self.clone
+      c.datasource = datasource
+      return c.valid?
+    end
+
+    # Return all charts that can be rendered
+    # out of the given datasource.
+    def self.charts_for_datasource(ds)
+      Chart.find(:all).select{|c| c.applyable?(ds)}
+    end
+    
+    # The current datasource
+    def datasource
+      if @mode == :preview
+	RandomDatasource.new([column1,column2,column3,"data"])
+      else
+	@datasource
+      end
+    end
+
+    # Set the current datasource
+    def datasource=(val)
+      raise "view only mode enabled. Cannot set new datasource." if mode == :view_only
+      @datasource = val
+    end
+
+    # Returns the current time range condition as a
+    # Condition Class
+    def date_condition
+      Condition.create("date","DATE",time_range) if time_range
+    end
+    
+    # Returns SQL conditions of the configured conditions in this chart.
+    def conditions(options = {})
+      conds = []
+      conds.push(@conditions) if @conditions
+      conds.push(date_condition.sql(datasource)) if date_condition
+      conds.push(options[:conditions]) if options[:conditions]
+      return conds.join(" AND ") if conds.length > 0
+    end
+    # Set new conditions. Not available if view mode is :view_only
+    def conditions=(val)
+      raise "view only mode enabled. Cannot set new conditions." if mode == :view_only
+      @conditions = val
+    end
+    
+    # Return wether the given column supports
+    # the type of chart. Currently only needed
+    # for line charts. Line charts need a
+    # numerical representation of the value.
+    def self.column_supports_type?(column, type)
+      case type
+      when "line"
+	return (column.number? or column.type == :time or column.type == :date)
+      else
+	return true
+      end
+    end
+
+    # Returns true if the field_name is (column1, column2...) contains
+    # a valid column name for the given (ds) or default datasource.
+    # options: :required, if field must contain a column, :asterix_allowed, if
+    # the field may contain an asterix 
+    def valid_column?(field_name, ds = nil, options = {})      
+      name = send(field_name)
+      return true if !options[:required].nil? and options[:required] == false and name.blank?
+
+      if col = (ds or datasource).table.columns_hash[name]
+	if options[:check_type] and !Chart.column_supports_type?(col, chart_type)
+	  errors.add(field_name, "Column type #{col.type} is not supported for chart #{chart_type}.")
+	  return false 
+	end
+	return true
+      end
+      return true if name == "*" and options[:asterix_allowed]
+      errors.add(field_name,"Invalid value '#{name}'.")
+      return false
+    end
+
+    # Set the data directory. Should be chart specific,
+    # function delete data removes the whole directory!
+    def set_data_directory(dir)
+      @data_directory = dir
+    end
+
+    # Data directory, where the chart data and the chart
+    # should be saved after rendering.
+    def data_directory
+      if @data_directory
+	p = Pathname.new(@data_directory)
+      else
+	p = Pathname.new(RAILS_ROOT) + "tmp/charts/#{query.hash}"
+      end
+      p.mkpath
+      p
+    end
+
+    # Removes the whole (!) data directory
+    def delete_data
+      d = data_directory
+      d.rmtree if d.exist? and d.directory?
+    end
+
+    # Filename of the chart
+    def image_name
+      "#{chart_type.camelize}Chart_#{height}_#{width}.png"
+    end
+
+    # Returns the yaml hash representing the chart object
+    def yaml_hash
+      # access attributes that may change values
+      if mode == :view_only
+	@yaml_hash
+      else
+        super
+      end
+    end
+
+    # overwriting yaml_hash for loading old charts
+    def yaml_hash=(val)
+      mode = :view_only
+      @yaml_hash = val
+    end
+
+    # Full basename of all files (including data patch)
+    def base_name; (data_directory + "chart").to_s; end
+    # Long full basename with main attributes in the name (type, width, height, hash).
+    def long_base_name; (data_directory + "chart_#{chart_type}_#{width}x#{height}_#{yaml_hash}").to_s; end
+
+    # Filename where data source is stored (.data)
+    def data_filename; base_name + ".data"; end
+    # Filename where image file is stored (.png)
+    def png_file_name; long_base_name + ".png"; end
+    # Filename where the imagemap for html pages is stored (.map)
+    def image_map_file_name; long_base_name + ".map";  end
+
+    # Date (mtime of data file) of stored data
+    def data_date;  File.mtime(data_filename) rescue nil; end
+    # Date (mtime of image file) of stored image
+    def image_date; File.mtime(png_file_name) rescue nil; end
+   
+    # Save yaml to the yaml file in the data directory (.yaml)
+    def save_yaml
+      open((base_name + "_#{yaml_hash}.yaml"),"w") {|f| f.write(self.to_yaml)}
+    end
+    
+    # Load Chart instance from a archive path, (data_dir can also be a yaml_hash)
+    def self.load_yaml(data_dir, yaml_hash)
+      if data_dir.to_s.to_i == 0
+        # data_dir is a real path
+	data_directory = Pathname.new(data_dir)
+	raise "Data '#{data_dir}' directory not found." unless data_directory.exist?
+      else
+        # data_dir is only a hash number, look in tmp path for it
+	data_directory =  Pathname.new(RAILS_ROOT) + "tmp/charts/#{data_dir}"
+      end
+
+      unless yaml_hash
+        # if no yaml hash provided, load the only one available,
+        # if more charts are available, raise exception
+	glob = Dir.glob((data_directory + "chart_*.yaml").to_s)
+	raise "More than one chart found in directory '#{data_directory}'." if
+	  glob.length > 1
+	raise "No chart found in directory '#{data_directory}'." if
+	  glob.length == 0
+	
+	glob[0] =~ /chart_(-?\d*).yaml$/
+	yaml_hash = $1
+	#	file = glob[0]
+      end
+
+      file = data_directory + "chart_#{yaml_hash}.yaml"
+
+      c = Chart.from_yaml(open(file,"r") {|f| f.readlines.join})
+      c.set_data_directory(data_directory)
+      c.mode = :view_only
+      c.yaml_hash = yaml_hash.to_i
+      c.freeze
+      c
+    end
+
+    # Path to alois java sourcepath
+    def Chart.java_src
+      Pathname.new(RAILS_ROOT + '/java/').realpath.to_s
+    end
+    # Java Classpath for execution
+    def Chart.java_cp
+      '/usr/share/java/jcommon.jar:/usr/share/java/jfreechart.jar:' + java_src
+    end
+
+    # Returns the java rendering command for render the chart, after
+    # execution of this command, the chart png file will exist (if no
+    # error occurred) 
+    def render_command()
+      raise "Deprecated: $display_environment, please remove this from your environment config (/etc/alois/environment.rb)." if $display_environment
+      disp = ""
+      #disp = if $display_environment then "DISPLAY=#{$display_environment} " else "" end
+      
+      params = { :category_column => category_column, :serie_column => serie_column, :range_column => range_column }.to_query
+      
+      "#{disp}/usr/bin/java -classpath \"#{Chart.java_cp}\" CreateChart '" + long_base_name + "' '<<LINK_ROOT>>#{params}'  #{chart_type} #{stacked} #{flipped}"
+    end
+    
+    # Returns the compile command to compile the java CreateChart.java file
+    def Chart.compile_command	    
+      ret = nil
+      cmd = "cd #{java_src}; /usr/bin/javac -nowarn -classpath \"#{java_cp}\" CreateChart.java 2>&1"
+      IO.popen(cmd,"r") {|out|
+	ret = out.readlines
+      }
+      throw "CreateChart compile error (#{cmd})\n #{ret.join("\n")}" unless $?.success?
+    end
+
+    # Chart column2, will be nil if database value is ""
+    def column2
+      val = super
+      return nil if val == ""
+      return val
+    end
+
+    # Chart column3, will be nil if database value is ""
+    def column3
+      val = super
+      return nil if val == ""
+      return val
+    end
+    
+    # get chart data, either from file if some data has already
+    # been stored on disc or a select to the previously defined
+    # datasource will be called.
+    def get_data(options = {})
+      if File.exists?(data_filename) and not options[:recreate_data]
+	data = YAML::load(File.open(data_filename))
+      else
+	ds = (options[:datasource] || datasource)
+	if ds.respond_to?(:data)
+	  data = ds.data
+	else
+	  data = ds.table.connection.select_all(query(options))
+	end
+	File.open(data_filename, 'w') { |f| f.puts data.to_yaml }
+      end
+      data
+    end
+
+    # Return cvs values of data (See get_data). For this, ruport
+    # is used
+    def to_csv
+      d = get_data
+      return "Sorry, no records." if d.length == 0
+      arr = []
+      d.each {|row| arr.push(row.dup)}
+      tbl = Ruport::Data::Table.new(:column_names => arr[0].keys)
+      arr.each {|row|
+	tbl << arr[0].keys.map {|k| row[k]}
+      }
+      
+      return tbl.to_csv
+    end
+
+    # Maximum amount of different values to display in
+    # chart. If maximum reached, the other values will be
+    # grouped into a separate value called REST
+    def max_values
+      if chart_type == "line"
+	return -1
+      else
+	super
+      end
+    end
+
+    # TODO: add comment
+    def guess_and_expand_names(names)
+      return names if names.length == 0 or names.compact.length != names.length
+      # check if these are dates and have correct order
+      if names.select {|n| n =~ /^\d\d\d\d-\d\d-\d\d$/}.length == names.length and
+	  names.sort == names and
+	  names[0].to_time < names[-1].to_time and
+	  ((names[-1].to_time - names[0].to_time) / 1.day).to_i.abs <= max_values
+	new_names = (names[0].to_date..names[-1].to_date).to_a.map{|d| d.strftime("%F")}
+      end
+      if names.select {|n| n =~ /^\d+$/}.length == names.length and
+	  names.sort {|x,y| x.to_i <=> y.to_i} == names and 
+	  names[0].to_i < names[-1].to_i and
+	  (names[-1].to_i - names[0].to_i).abs <= max_values
+	new_names = (names[0].to_i..names[-1].to_i).to_a.map{|d| d.to_s}
+      end
+      return names unless new_names
+      return new_names.map {|nn| if names.include?(nn) then nn else nn + "*" end }
+    end
+
+    # TODO: add comment
+    def traverse_table(table, names_arr, aggregation_columns, data_column, sum = false)
+      ret = {}
+      if names_arr.length > 0
+	names_arr[0].each {|name|
+	  ret[name] = traverse_table(table.select{|r| r[aggregation_columns[0]] == name},
+				      names_arr[1..-1],
+				     aggregation_columns[1..-1],
+				     data_column)
+	}
+
+	# if there are any records left, add them as rest
+	rest_table = table.reject{|r| names_arr[0].include?(r[aggregation_columns[0]])}
+	if rest_table.length > 0
+	  ret[REST_VALUE] = traverse_table(rest_table,
+				       names_arr[1..-1],
+				       aggregation_columns[1..-1],
+				       data_column,true)
+	  names_arr[0].push(REST_VALUE)
+	end
+      else
+	return 0 if table.length == 0
+	if true
+	  return table.map {|r|r[data_column].to_i}.sum
+	else
+	  throw "more than one value left" if table.length > 1
+	  return table[0][data_column].to_i
+	end
+      end
+      return ret
+    end
+
+    # TODO: add comment
+    def transform_table(table, aggregation_columns, data_column)
+      names =[]
+      aggregation_columns.each {|col|
+	names.push(table.map {|row| row[col]}.uniq[0..max_values])
+      }
+      return names, traverse_table(table, names, aggregation_columns, data_column)
+    end
+
+    # Placeholder for nil values
+    NIL_VALUE = "<<NULL>>"
+    # Placeholder for rest value
+    REST_VALUE = "<<REST>>"
+
+    # write value to the pipes and replace
+    # nil value with placeholder (See NIL_VALUE)
+    def my_write(pipes, value)
+      pipes.write("#{(value || NIL_VALUE).to_s.strip.gsub(/\n|\r/,"")}\n")
+    end
+
+    # Map value to number (used for line charts
+    # eg. date and times can be displayed as
+    # a float). In later versions the java class
+    # for date and times should be used.
+    def self.map_to_number(val)
+      case val
+      when /(\d\d):(\d\d):(\d\d)/
+	hour = $1.to_f
+	hour += $2.to_f / 60.0
+	hour += $3.to_f / 60.0 / 60.0
+	return "#{hour}"
+      when /(\d\d\d\d)-(\d\d)-(\d\d)/	
+	cur = (DateTime.parse("#{$1}-#{$2}-#{$3}") - DateTime.parse("#{$1}-01-01")).to_i
+	tot = (DateTime.parse("#{$1.to_i + 1}-01-01") - DateTime.parse("#{$1}-01-01")).to_i
+	return ("%.3f" % ($1.to_f + cur.to_f / tot.to_f))
+#      when /(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)/
+#	year = $1.to_i
+#	year += $2.to_f / 12.0
+#	year += $3.to_f / 12.0 / 31.0
+#	year = $4.to_i / 365.0
+#	year += $5.to_f / 365.0 / 60.0
+#	year += $6.to_f / 365.0 / 60.0 / 60.0
+#	return "#{year}"
+        #      end
+      else
+        val.to_f
+      end
+    end
+
+    # remap a integer value to a date (See map_to_number)
+    def self.map_to_date(value)
+      year = value.to_i
+      tot = (DateTime.parse("#{year + 1}-01-01") - DateTime.parse("#{year}-01-01")).to_i
+      day = ((value.to_f - year.to_f) * tot.to_f).round
+      (DateTime.parse("#{year}-01-01") + day).strftime("%F")
+    end
+
+    # remap a float value to a time (See map_to_number)
+    def self.map_to_time(value)
+      value = value.to_f
+      hour = value.to_i
+      value = (value - hour.to_f) * 60.0
+      minute = value.to_i
+      second = ((value - minute.to_f) * 60.0).round
+      value = "#{hour.to_s.rjust(2,"0")}:#{minute.to_s.rjust(2,"0")}:#{second.to_s.rjust(2,"0")}"
+    end
+
+    # Render this configured chart into png, html and imagemap.
+    # If a up to date version is available, render will do nothing.
+    # See also CACHING_TIMEOUT
+    def render(options = {})
+      # Do nothing if chart has already been rendered and
+      # caching timeout has not been reached yet
+      return if File.exist?(png_file_name) and File.exists?(image_map_file_name) and not (image_date < data_date rescue false) and not options[:recreate_data] and not (data_date > CACHING_TIMEOUT.minutes.ago and mode != :view_only)
+
+      # View only charts are loaded from a directory, they should
+      # already be rendered.
+      raise "Cannot render, viewing only!" if mode == :view_only
+
+      # Store values in chart attributes for later serialization
+      @table_name = (options[:datasource] || datasource).table.table_name
+      @option_conditions = options[:conditions]
+
+      # external link will be used for imagemap
+      @external_link  = "#{$root_url}table/#{@table_name}/chart_click?" +
+	{:default_filter => conditions(options)}.to_query + "&"
+
+      # save the created information into yaml, so nothing
+      # can be changed later.
+      save_yaml
+      $log.debug{"Getting data for chart."}
+      # Query or load data for chart
+      result = get_data(options)
+
+      $log.debug{"Prepare data for chart."}
+      if chart_type == "line"
+        # Map values to numbers for that a line can be rendered
+	result.each {|row|
+	  row[category_column] = Chart.map_to_number(row[category_column])
+	}
+      end
+      
+      # Compile java application if we are working in
+      # a develepment environment.
+      Chart.compile_command if RAILS_ENV == 'development'
+      cmd = "#{render_command()} 2>&1 > '#{long_base_name}.err'"
+
+      # aggregation columns
+      agg_cols = [column3,column2,column1]
+
+      # TODO: Comment this function till the end
+      all_names, tree = transform_table(result, agg_cols, "data")
+      if chart_type != "lines" and chart_type != "line"
+	all_names = all_names.map {|n| guess_and_expand_names(n) }
+      end
+
+      category_names = all_names[2]
+      serie_names = all_names[1]
+      range_names = all_names[0]
+
+      begin
+	#	Timeout.timeout(10){	
+	$log.debug{"Sending data to java app."}
+	
+	IO.popen(cmd,"w") {|pipes|
+
+	  my_write(pipes,"#{category_column.pluralize.humanize} from #{datasource.name}" + if conditions(options) then " WHERE #{conditions(options)}" else "" end)
+	  my_write(pipes,"#{width}")
+	  my_write(pipes,"#{height}")
+ 	  
+	  # xaxis
+	  my_write(pipes, category_column)
+	  # yaxis
+	  my_write(pipes, data_column)
+	  
+	  # series	  
+	  my_write(pipes, serie_names.length)
+	  
+	  # ranges
+	  my_write(pipes, range_names.length)
+	  
+	  range_names.each {|range|
+	    if range_column.nil?
+	      my_write(pipes, nil)
+	    else
+	      if range == REST_VALUE
+		my_write(pipes, range)
+	      else
+		my_write(pipes,"#{range_column}=#{range or NIL_VALUE}")
+	      end
+	    end
+	    
+	    serie_names.each {|serie|
+	      if serie_column.nil?
+		my_write(pipes, nil)
+	      else
+		if serie == REST_VALUE
+		  my_write(pipes, serie)
+		else
+		  my_write(pipes,"#{serie_column}=#{serie or NIL_VALUE}")
+		end
+	      end
+	      
+	      my_write(pipes, category_names.length)
+	      my_write(pipes, 1)
+	      
+	      category_names.each {|name| 
+		if name and name.to_s.length >= MAX_VALUE_LENGTH
+		  my_write(pipes,"#{name}%")
+		else
+		  my_write(pipes, name)
+		end
+		my_write(pipes,(((tree[range] or {})[serie] or {})[name] or 0))
+	      }
+	    }
+	  }
+	  $log.debug{"Closing java pipes."}
+	  pipes.close
+	  $log.debug{"Java render done."}
+	}
+
+	unless $?.success?
+	  ret = nil
+	  open(long_base_name + ".err") {|f| ret = f.readlines.join("\n")} if File.exist?(long_base_name + ".err")
+	  throw "Image render failed (#{cmd}).\n #{ret}" 
+	end
+
+	# correct bug that the category begins always with a '?'
+	map = open(image_map_file_name).readlines.join
+	map.gsub!("?category=","&category=")
+	map.gsub!("?series=","&series=")
+	open(image_map_file_name,"w") {|f| f.write(map)}
+
+      rescue Timeout::Error
+	msg = open(long_base_name + ".err").readlines.join("\n")
+	Dir.glob(long_base_name + ".*").each {|f|
+	  File.delete(f)
+	}
+	
+	throw "Generating graphic failed.\n#{msg}"
+      end
+
+    end
+
+    # Construct the image_tag string for this chart (displayed on mouseover)
+    def image_tag(options = {})      
+      real_file = (Pathname.pwd + png_file_name).realpath.to_s
+      if options[:url]
+	params = {:action => "chart_image",
+	  :chart_yaml_hash => yaml_hash}
+	if @data_directory
+	  params[:chart_data_dir] = @data_directory.to_s
+	else
+	  params[:chart_tmpdir_hash] = query.hash.to_s
+	end
+	url = options[:url] + "&" + params.to_query
+      end
+      
+      if options[:relative_path]
+	relative_path = Pathname.new(options[:relative_path]).realpath.to_s
+	
+	if real_file =~ Regexp.new("^#{Regexp.escape(relative_path + "/")}(.*)$")
+	  url = $1
+	else
+	  raise "'#{real_file}' is no subpath of '#{relative_path}'."
+	end
+      end
+      
+      if options[:absolute_path]
+	url = real_file	
+      end
+
+      mapname = "#chart"
+      mapname += options[:chart_number].to_s if options[:chart_number]
+
+      "<IMG SRC=\"#{url}\" WIDTH=\"#{width}\" HEIGHT=\"#{height}\" BORDER=\"0\" USEMAP=\"#{mapname}\">"
+    end
+
+    # Image map text to be included into HTML
+    def image_map(options = {})
+      map = open(image_map_file_name).readlines.join("\n")
+      map.gsub!('id="chart"',"id=\"chart#{options[:chart_number]}\"") if options[:chart_number]
+      map.gsub!('name="chart"',"name=\"chart#{options[:chart_number]}\"") if options[:chart_number]
+      map.gsub!("<<LINK_ROOT>>",(options[:link] or @external_link))
+      map
+    end
+        
+    # The aggregation value to be used. Default
+    # value * has no special function (normal group by)
+    # See also AGGREGATION_FUNCTIONS
+    def aggregation_column
+      super or "*"
+    end
+
+    # Selected chart type See CHART_TYPES
+    def chart_type
+      (super or "bar").singularize
+    end
+
+    # Returns supported aggregation fuctions
+    AGGREGATION_FUNCTIONS = ["COUNT","SUM","MIN","MAX"]
+
+    # Selected aggregation function
+    def aggregation_function
+      super or "COUNT"
+    end
+    
+    # Combined data_column from aggregation_function and aggregation_column
+    # Default value would be COUNT(*). Or other values could be: SUM(size), MIN(start_time)
+    def data_column
+      "#{aggregation_function}(#{aggregation_column})"
+    end
+
+    # Returns a list of possible orders for the data to be displayed
+    # in the chart. Enumeration for selectbox with different combinations
+    # of aggregation_columns and aggregation_functions
+    def possible_orders
+      if datasource
+	orders = []
+
+	AGGREGATION_FUNCTIONS.each {|agg|
+	  orders.push("#{agg}(*)")
+	  orders.push("#{agg}(*) DESC")	    
+	}
+
+	datasource.table.columns.each {|col1|
+	  orders.push("#{col1.name}")
+	  orders.push("#{col1.name} DESC")	  
+	  AGGREGATION_FUNCTIONS.each {|agg|
+	    orders.push("#{agg}(#{col1.name})")
+	    orders.push("#{agg}(#{col1.name}) DESC")
+	  }
+	}
+
+	orders.push(self.real_order_by) unless orders.include?(self.real_order_by)
+
+	return orders.sort
+      else
+	raise "No datasource defined."
+      end
+    end
+
+    # possilbe names for ordering
+    ORDER_NAMES = ["column1","data_column","column2","column3"]
+
+    # Return the correct column_name (column1, data_column...)
+    # out of the order_by statement
+    def order_by_column(number)
+      col = (order_by.split(",")[number] or "").strip
+      return "none" if col == ""
+      
+      ORDER_NAMES.each {|c|
+	return c if self.send(c) == col
+	return "#{c}_desc" if "#{self.send(c)} DESC" == col
+      }
+      return "none"
+    end
+
+    # Set a order by column
+    def set_order_by_column(number, column)
+      return if column == ""      
+     
+      desc = false
+      if column =~ /^(.*)_desc$/
+	column = $1
+	desc = true
+      end
+
+      if ORDER_NAMES.include?(column)
+	val = self.send(column)
+	val = "#{val} DESC" if desc 
+      else
+	val = column
+      end
+      
+      orders = order_by.split(",") #.map {|o| 
+#	if o =~ /^\s*(\w+)(\s+DESC)?\s*$/i
+#	  $1
+#	else
+#	  throw "unrecognized order '#{o}'"
+#	end
+#      }
+#      p [orders, number, column, val]
+
+      if orders.length >= (number + 1)
+	orders[number] = val
+      end
+      if orders.length == number
+	orders.push(val);
+      end
+      self["order_by"] = orders.join(",")
+    end
+
+    # TODO: this should be reworked
+    def order_1st
+      order_by_column(0)
+    end
+    # TODO: this should be reworked
+    def order_1st=(val)
+      set_order_by_column(0,val)
+    end    
+    # TODO: this should be reworked
+    def order_field_1st; order_by.split(",")[0] end
+    # TODO: this should be reworked
+    def order_field_1st=(val); self.order_1st = val if self.order_1st == "none"; end
+
+    # TODO: this should be reworked
+    def order_2nd
+      order_by_column(1)
+    end
+    # TODO: this should be reworked
+    def order_2nd=(val)
+      set_order_by_column(1,val)
+    end
+    # TODO: this should be reworked
+    def order_field_2nd;  order_by.split(",")[1] end
+    # TODO: this should be reworked
+    def order_field_2nd=(val); self.order_2nd = val if self.order_2nd == "none"; end
+
+    # TODO: this should be reworked
+    def order_3rd
+      order_by_column(2)
+    end
+    # TODO: this should be reworked
+    def order_3rd=(val)
+      set_order_by_column(2,val)
+    end
+    # TODO: this should be reworked
+    def order_field_3rd;  order_by.split(",")[2] end
+    # TODO: this should be reworked
+    def order_field_3rd=(val); self.order_3rd = val if self.order_3rd == "none"; end
+
+    # Return order_by of the database or
+    # generate a menfull default.
+    def order_by
+      s = super
+      s = nil if super == ""
+      s or
+	if column2
+	  "#{column1}, #{column2}"
+	else
+	  "#{data_column} DESC"
+	end
+    end
+    
+    # returns a SQL usabel order by statement
+    def real_order_by
+      order_by.split(",").reject {|o| o == "none"}.join(",")
+    end
+
+    # Generate a SQL statement to get data for rendering this chart
+    def query(options = {})
+
+      col = datasource.table.columns.select{|s| s.name ==column1}[0]
+      r = ""
+      if col.type == :string or col.type == :binary
+	r += "SELECT substr(#{column1},1,#{MAX_VALUE_LENGTH}) as #{column1}"
+      else
+	r += "SELECT #{column1}"
+      end	
+      r += ", #{column2}" if column2
+      r += ", #{column3}" if column3
+      r += ", #{data_column}"
+      r += " AS data FROM "
+      if datasource.table.respond_to?(:override_query) and 
+	  (m_query = datasource.table.override_query(:conditions => conditions(options)))
+	r += "(#{m_query}) AS mod_query"
+      else
+	r += "#{datasource.table.table_name}"
+      end
+      r += " WHERE #{conditions(options)}" if conditions(options)
+      r += " GROUP BY "
+      r += "#{column3}, " if column3
+      r += "#{column2}, " if column2
+      r += "#{column1} ORDER BY " + real_order_by
+      return r
+    end
+
+#    def max_bars=(val)
+#      @max_bars = val.to_i
+#    end
+#    def max_bars
+#      return @max_bars if @max_bars
+#      m = (width / 22)
+#      m = m / 2 if column2
+#      return m.to_i
+#    end
+
+    # get zoom (1.0 is no zoom with defined height and weight)
+    def zoom
+      (((@zoom or 1).to_f * 100).round)/100.0
+    end
+    # set zoom (1.0 is no zoom with defined height and weight)
+    def zoom=(value)
+      @zoom = value
+    end
+
+    # Default width for image rendering in pixel (with zoom 1.0)
+    def width
+      self["width"] = DEFAULT_WIDTH unless super
+      w = super
+      w = (w * zoom).to_i if zoom
+      w	
+    end
+
+    # Default height for image rendering in pixel (with zoom 1.0)
+    def height
+      self["height"] = DEFAULT_HEIGHT unless super
+      h = super
+      h = (h * zoom).to_i if zoom      
+      h	
+    end
+
+    ### Ossim stuff, deprecated?
+
+    CUSTOM_GRAPH = "/../ossim/panel/custom_graph.php"
+
+    def image_url_params
+      {:controller => CUSTOM_GRAPH, :params => ossim_params}
+    end
+
+  end

Added: incubator/alois/trunk/rails/app/models/charts_report_template.rb
URL: http://svn.apache.org/viewvc/incubator/alois/trunk/rails/app/models/charts_report_template.rb?rev=1031127&view=auto
==============================================================================
--- incubator/alois/trunk/rails/app/models/charts_report_template.rb (added)
+++ incubator/alois/trunk/rails/app/models/charts_report_template.rb Thu Nov  4 18:27:22 2010
@@ -0,0 +1,20 @@
+# Copyright 2010 The Apache Software Foundation.
+# 
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# 
+# http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Model for saving chart templates
+class ChartsReportTemplate < ActiveRecord::Base
+  # for fixtures :all in test_helper,
+  # otherwiese errors are produced in
+  # test.log
+end

Added: incubator/alois/trunk/rails/app/models/condition.rb
URL: http://svn.apache.org/viewvc/incubator/alois/trunk/rails/app/models/condition.rb?rev=1031127&view=auto
==============================================================================
--- incubator/alois/trunk/rails/app/models/condition.rb (added)
+++ incubator/alois/trunk/rails/app/models/condition.rb Thu Nov  4 18:27:22 2010
@@ -0,0 +1,191 @@
+# Copyright 2010 The Apache Software Foundation.
+# 
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# 
+# http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+  # Condition class
+  class Condition
+
+    # List of all supported Operators
+    OPERATORS = [ "=", "!=", 
+      "LIKE", "NOT LIKE",
+      ">",">=",
+      "<","<=",
+      "IS NULL","IS NOT NULL",
+      "SQL","DATE","IPRANGE","FILTER"] unless defined?(OPERATORS)
+
+
+    # options can either be a table_class or a hash of options.
+    def normalize_options(options = {})
+      return options if options.class == Hash
+      {:table_class => options}
+    end
+
+    # Return true if given options are valid
+    def valid?(options = {})
+      options = normalize_options(options)
+      validate(options)
+      return true
+    rescue
+      false
+    end
+    
+    
+    # raises an exception if the given options are not valid
+    def validate(options = {})
+      options = normalize_options(options)
+      self.sql(options[:table_class])
+      raise "No column defined." if column.nil? or column.strip == ""
+      if options[:table_class]
+	raise "Condition not appliable to #{options.inspect}." unless applyable(options)
+      end
+    end
+
+    # Default is always true. Updatable means if the
+    # condition can be updated with new properties from UI.
+    def updatable?
+      true
+    end
+    
+
+    # Creates a appropriate condition class, sql-, date-, iprange-, any-, filter-condition
+    def self.create(column, operator, value, options = {})
+      case operator
+      when "SQL"
+	return SqlCondition.new(value, options)
+      when "DATE"
+	return DateCondition.new(column, operator , value)
+      when "IPRANGE"
+	return IpRangeCondition.new(column, operator , value)	
+      when "FILTER"
+	return FilterCondition.new(column, operator, value)
+      else
+	if column == "ANY" then
+	  return AnyCondition.new(operator,value,options)
+	else
+	  return Condition.new(column, operator, value, options )
+	end
+      end
+    end
+
+    # Finds a column in the table_class. If there is no such column, nil will be returned.
+    def find_column(name, options = {})
+      options = normalize_options(options)
+      return nil unless (table_class = options[:table_class])
+      
+      name =~ /(.*\.)?(.*)/
+      return nil if 
+	$1 != nil and 
+	$1 != table_class.table.table_name
+      
+      for col in table_class.table.columns
+	return col if col.name == $2 	
+      end
+      return nil
+    end
+
+    # Returns if there is a column with that name
+    def has_column?(name,options = {})
+      return !!find_column(name,options)
+    end
+    
+    attr_reader :operator, :value
+    # Initializes a new condition
+    def initialize(column, operator, value, options = {})
+      return super(column) if column.instance_of?(Hash)
+      throw "No column given." unless column
+      throw "No operator given." unless operator
+
+      @column = column
+      @operator = operator
+      @value = value  
+      if @value == nil
+	@operator = "IS NULL" if @operator == "="
+	@operator = "IS NOT NULL" if @operator == "!="
+      end
+      @value = nil if @operator == "IS NULL" || @operator == "IS NOT NULL"
+      @options = options
+      #    value = @conditions[i][2]
+      #    value = [value] if value.class.name == "String"
+      #    value = [] if value == nil
+      #    ret = ""
+      #    for val in value 
+      #      ret += " OR " if ret != ""
+      #      ret += "#{@conditions[i][0]} #{@conditions[i][1]} #{val}"
+      #    end
+      #    return "(#{ret})"
+    end
+    
+    # returns the column string for use in SQL (including tablename if information available)
+    def column(options = {})
+      options = normalize_options(options)      
+      return "" if @column == ""
+      @column =~ /(.*\.)?(.*)/
+      if (table_class = options[:table_class])
+	"#{table_class.table.table_name}.`#{$2}`"
+      else
+	"#{$1}`#{$2}`"
+      end
+    end
+    
+    # returns the quoted value to use in SQL
+    def quoted_value(options = {}) 
+      options = normalize_options(options)
+      col = find_column(@column,options)
+      return ActiveRecord::Base.quote_value(value) unless col 
+      return ActiveRecord::Base.quote_value(col.type_cast(value)) if col.number?
+      return ActiveRecord::Base.quote_value(value)
+    end
+
+    # Returns the condition to use in SQL
+    def sql(options = {})
+      options = normalize_options(options)
+      if value	
+	"#{column(options)} #{operator} #{quoted_value(options)}"
+      else
+	"#{column(options)} #{operator}"
+      end
+    end
+    
+    # Returns weather the condition is appliable.
+    def applyable(options = {})
+      options = normalize_options(options)
+      return true if options[:table_class].nil?
+      return has_column?(@column,options)
+    end
+
+    # Update the condition from ui params
+    def update_from_params(params)
+      @value = params[:value] if params
+    end
+
+    # TODO: probably this is deprecated
+    def descriptions
+#      ["Column name or name of the rule.","Column/Name",
+#        nil,nil,nil,conditions[i][0],nil],
+#	["Operator for the rule.","Operator",
+#        nil,nil,nil,conditions[i][1],nil],
+#	["Value to compare for the rule.","Value",
+#        nil,nil,nil,value,nil],
+#	["Percentage of items matched this rule.","%",
+#        get_condition(i), nil, "ALL", nil, "del_condition","d"],
+#	["All values matched by that rule, and are in this sample.","In %",
+#        get_condition_string(true), nil, get_condition(i), nil,nil,nil],
+#	["All values matched by that rule, that are not in this sample.","Not In %",
+#        get_condition(i), get_condition_string(true) , get_condition(i), nil,'negate_all_but','>'],
+#	["How many records more would be in the sample if this rule where deleted.","Banish",
+#        get_condition_string(true,i),get_condition_string(true), nil, nil,'banished', '>'],
+#	["Disable/Enable the rule.","",
+#        nil, nil, nil, if conditions[i][3] then "on" else "off" end, 'toggle', '>']
+      
+    end
+  end

Added: incubator/alois/trunk/rails/app/models/connection.rb
URL: http://svn.apache.org/viewvc/incubator/alois/trunk/rails/app/models/connection.rb?rev=1031127&view=auto
==============================================================================
--- incubator/alois/trunk/rails/app/models/connection.rb (added)
+++ incubator/alois/trunk/rails/app/models/connection.rb Thu Nov  4 18:27:22 2010
@@ -0,0 +1,147 @@
+# Copyright 2010 The Apache Software Foundation.
+# 
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# 
+# http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+class Connection < ActiveRecord::Base
+  @@connections = {}
+  def Connection.connections; @@connections; end
+
+  def Connection.activerecord_connection(name)
+    conn = @@connections[name]
+    raise "Connection with name #{name} not registered" unless conn
+    conn.activerecord_connection
+  end
+
+  def Connection.from_name(name)
+    @@connections[name]
+  end
+
+  def Connection.from_pool(pool)
+    @@connections.each {|name, connection|
+      return connection if connection.pool == pool
+    }
+    return nil
+  end
+
+  def Connection.register(conn)
+    @@connections[conn.name] = conn
+  end
+
+  attr_accessor :pool
+
+  def activerecord_connection
+    raise "Connection #{name} ont yet registered" unless @pool
+    @pool.connection
+  end
+
+  def spec
+    self.attributes.symbolize_keys    
+  end
+
+  def connection_handler
+    ActiveRecord::Base.connection_handler
+  end
+  
+  def register
+    raise "Already registered connection #{name}" if @pool
+    raise "Connection #{name} already registered globally" if @@connections[name]
+
+    @pool = connection_handler.establish_connection(name, ConnectionSpecification.new(spec, "#{spec[:adapter]}_connection"))
+    Connection.register(self)
+  end
+
+  def unregister
+    raise "Connection #{name} not registered" unless @pool
+
+    # compare connection pool
+    connection_pools = connection_handler.connection_pools
+    other_connections = connection_pools.select { |key, value| 
+      value == @pool 
+    }.map {|key,value| key}
+
+    raise "Connection #{name} not in connection pool!" if other_connections.length == 0
+    raise "Other klasses need this connection #{other_connections}" if
+      other_connections.length > 1    
+
+    connection_pools.delete_if {|key,value| value == @pool}
+    @pool.disconnect!
+#    conf = @pool.spec.config
+    @pool = nil
+#    conf
+  end
+  
+  def status
+    case adapter
+    when "mysql"
+      execute("SHOW INNODB STATUS").fetch_hash["Status"]      
+    when /sqlite/
+    else
+      "No status available for adapter #{adapter}"
+    end
+  end
+
+  def execute(sql)
+    activerecord_connection.execute(sql)
+  end
+
+  def auto_increment(table_name)
+    connection_auto_increment(activerecord_connection, table_name)
+  end
+    
+  # old mysql function
+  # def flush_tables
+  #   execute("flush tables")
+  # end
+  def approx_count(table_name)
+    connection_approx_count(activerecord_connection, table_name)
+  end
+  
+  def current_queries
+    case adapter
+    when "mysql"
+      ret = []
+      res = execute("SHOW FULL PROCESSLIST")
+      while col = res.fetch_hash
+        ret.push(col)
+      end
+      return ret
+    else
+      return nil
+    end
+  end
+
+  def kill(query_id)
+    case adapter
+    when "mysql"
+      execute("KILL #{query_id.to_i}")
+    else
+      raise "Kill queries not implemented for #{adapter}"
+    end
+  end
+      
+  def variables_text
+    hash = {}
+    case adapter
+    when "mysql"      
+      res = execute("SHOW VARIABLES")
+      while col = res.fetch_hash
+        hash[col["Variable_name"]] = col["Value"]
+      end
+      ml = hash.keys.map {|k| k.length}.max
+    else
+    end
+
+    hash.keys.sort.map {|key| key.ljust(ml," ") + ": " + hash[key].to_s }.join("\n")
+  end
+
+end

Added: incubator/alois/trunk/rails/app/models/date_condition.rb
URL: http://svn.apache.org/viewvc/incubator/alois/trunk/rails/app/models/date_condition.rb?rev=1031127&view=auto
==============================================================================
--- incubator/alois/trunk/rails/app/models/date_condition.rb (added)
+++ incubator/alois/trunk/rails/app/models/date_condition.rb Thu Nov  4 18:27:22 2010
@@ -0,0 +1,81 @@
+# Copyright 2010 The Apache Software Foundation.
+# 
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# 
+# http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+  # Condition for matching against date columns
+  class DateCondition < Condition
+
+    # For usage in UI
+    def self.get_date_descriptions
+      values_help_text.keys.map { |v| v.to_s }
+    end
+    # Descriptions for special value parameters
+    def self.values_help_text
+      { :today => "The current date.",
+	:yesterday => "The yesterday's date."
+      }
+    end
+
+    # Returns the time range (converts a "prosa" value into a range, eg today)
+    def range(options = {})
+      value.to_time_range((options[:now] or Time.now)) 
+    end
+
+    # Returns true if value describes a whole day
+    def exact_date?(options = {})
+      from_date(options) == to_date(options)
+    end
+
+    # The starting date of the range
+    def from_date(options = {})
+      range(options).first.strftime("%F")
+    end
+
+    # The starting time of the range
+    def from_time(options = {})
+      t = range(options).first.strftime("%T")
+      return nil if t == "00:00:00"	
+      t
+    end
+
+    # The ending date of the range
+    def to_date(options = {})
+      range(options).last.strftime("%F")
+    end
+    
+    # The ending time of the range
+    def to_time(options = {})
+      t = range(options).last.strftime("%T")
+      return nil if t == "23:59:59"
+      t
+    end
+
+    # Returns the SQL condition
+    def sql(options = {})
+      options = normalize_options(options)
+      ret = ""
+
+      if exact_date?(options)
+	ret += "#{column} = '#{self.from_date(options)}' "
+      else
+	ret += "#{column} >= '#{self.from_date(options)}' AND "	
+	ret += "#{column} <= '#{self.to_date(options)}' "	
+      end
+
+      raise "not time selection supported yet '#{range(options)}' please use beginning and end." if from_time or to_time
+
+      return nil if ret == ""
+      return ret
+    end
+    
+  end

Added: incubator/alois/trunk/rails/app/models/default_schema_migration.rb
URL: http://svn.apache.org/viewvc/incubator/alois/trunk/rails/app/models/default_schema_migration.rb?rev=1031127&view=auto
==============================================================================
--- incubator/alois/trunk/rails/app/models/default_schema_migration.rb (added)
+++ incubator/alois/trunk/rails/app/models/default_schema_migration.rb Thu Nov  4 18:27:22 2010
@@ -0,0 +1,72 @@
+# Copyright 2010 The Apache Software Foundation.
+# 
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# 
+# http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+  class DefaultSchemaMigration < ActiveRecord::Base
+    set_table_name "schema_migrations"
+    
+    def self.version
+      self.find(:all).sort_by {|m| m.version.to_i}[-1].version.to_i
+    end
+
+    def self.dump_schema_version
+      open(schema_version_file,"w").write(self.version)
+    end
+
+    def self.schema_version_file(db = nil)
+      db = ENV['DEFAULT_CONNECTION'] unless db
+      "#{RAILS_ROOT}/app/models/packaged_for_#{db}_schema_version"
+    end
+
+    def self.desired_version_from_dump_file(db)
+      return nil unless File.exists?(schema_version_file(db))
+      open(schema_version_file(db),"r").read().to_i
+    end
+    
+    def self.desired_version_from_migration_files(db)
+      Dir.glob("#{RAILS_ROOT}/db_#{db}/migrate/*.rb").sort.reverse.each { |f|
+	if Pathname.new(f).basename.to_s =~ /^(\d\d\d)_.*\.rb$/
+	  return $1.to_i
+	end
+      }
+      return nil
+    end
+
+    def self.check_schema_versions!
+      check_schma_version!("alois")
+      check_schma_version!("pumpy")
+    end
+
+    def self.check_schma_version!(db)
+      desired_version = desired_version_from_dump_file(db) 
+      desired_version = desired_version_from_migration_files(db) unless desired_version
+
+      throw "Could not get the desired db version for database #{db}!" unless desired_version
+      case db
+      when "alois"
+	current_version = AloisSchemaMigration.version	
+      when "pumpy"
+	current_version = PumpySchemaMigration.version
+      else
+	throw "No such database '#{db}'."
+      end
+      
+      if current_version == desired_version 
+	$log.info{"Database version for db '#{db}' match! (curr: '#{current_version}', desired: '#{desired_version}')"}
+      else
+	msg = "Database version mismatch for db '#{db}'! (curr: '#{current_version}', desired: '#{desired_version}')"
+	$log.error{msg}
+	throw msg
+      end	
+    end
+  end

Added: incubator/alois/trunk/rails/app/models/filter.rb
URL: http://svn.apache.org/viewvc/incubator/alois/trunk/rails/app/models/filter.rb?rev=1031127&view=auto
==============================================================================
--- incubator/alois/trunk/rails/app/models/filter.rb (added)
+++ incubator/alois/trunk/rails/app/models/filter.rb Thu Nov  4 18:27:22 2010
@@ -0,0 +1,137 @@
+# Copyright 2010 The Apache Software Foundation.
+# 
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# 
+# http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+  # TODO: document difference between appliable and valid
+  class Filter < ActiveRecord::Base
+    validates_presence_of :name
+    validates_uniqueness_of :name
+
+    attr_accessor :datasource
+
+    # Validates weather all conditions are valid for source
+    validate do |f|
+      i = 0
+      errors = f.conditions.map {|c|
+	i+=1
+	begin
+	  if f.datasource
+	    c.validate(f.datasource.table)
+	  else
+	    c.validate
+	  end
+	  nil
+	rescue
+	  "\##{i}: #{$!}"
+	end
+      }.compact
+      f.errors.add('yaml_declaration',errors.join(", ")) if errors.length > 0
+    end
+
+    # Parses text and returns a list of filters
+    def self.parse_text_field(text)
+      return [] if text.nil? or text.strip == ""
+      return text.split(",").map {|filter_id| 
+	raise "Filterid '#{filter_id}' is invalid." unless filter_id.strip =~ /^\d+$/
+	Filter.find(filter_id.strip)
+      }      
+    end
+
+    # Create new Filter based on UI parameters
+    def self.create_from_params(params)
+      if params[:filter_id] then
+        f = find(params[:filter_id])
+        throw "Filter with id '#{params[:filter_id]}' not found!" if f == nil
+      end
+      if params[:filter_name] then
+        f = find_by_name(params[:filter_name])
+        throw "Filter with name '#{params[:filter_name]}' not found!" if f == nil
+      end
+      if f == nil then
+        Filter.table_name = 'filters'
+        f = Filter.new
+	throw params[:conditions]
+        f.yaml_declaration = params[:conditions]
+      end
+      return f if f
+      throw "Could not create filter of parameters."
+    end
+
+    # Returns all conditions stored in the yaml_declaration field
+    def conditions
+      c = [DateCondition,SqlCondition,AnyCondition,IpRangeCondition,FilterCondition]
+      return [] unless self.yaml_declaration
+      y = YAML.parse(self.yaml_declaration)
+      return [] unless y
+      y.transform 
+    end
+
+    # Set new condition array
+    def conditions=(c)
+      throw c unless c.class == Array
+      self.yaml_declaration = c.to_yaml
+    end
+
+    # Returns a array of SQL conditions for that filter
+    def sql(options = {})      
+      table = options[:table_class]	
+      if table.respond_to?(:do_not_use_view_for_query) and table.do_not_use_view_for_query
+	conditions.map {|c| c.sql(options) }
+      else
+	conditions.map {|c| c.sql(options[:table_class]) }
+      end
+    end
+
+    # Returns if all condtions can be applied to the table
+    def valid_for_table?(table)
+      conditions.reject {|c| c.valid?(table)}.length == 0
+    end
+
+    # Returns the SQL conditions for that filter, this can be used in where clause of a query.
+    def get_condition_string(table, real = false, withoutrule = -1,options = {})      
+      ret = conditions.select{|c| c.valid?(table) }.map {|c| c.sql(table) }
+      ret[withoutrule] = nil if withoutrule != -1
+#      if not options[:without_date]
+#	dc = DateCondition.new("date", from_date, to_date, time_description)
+#	ret << [dc.sql]	  
+#      end
+      ret = ret.compact
+      ret = ret.join(" AND ")
+      return nil if ret == ""
+      return ret
+      
+#      if not real then
+#	if options[:negative_set] then
+#	  ret = "1=1" if ret == nil
+#	  ret = "NOT (#{ret})"
+#	end
+#	if options[:global_rule_number] then
+#	  if options[:banished_set] then
+#	    c = get_condition_string(true,options[:global_rule_number])
+#	    c = "1=1" if c == nil
+#	    ret += " AND #{c}"
+#	  else
+#	    ret += " AND #{get_condition(options[:global_rule_number])}"
+#	  end
+#	end
+#      end      
+    end   
+    
+    # Return true if all conditions are appliable
+    def applyable(table_class)
+      for condition in conditions
+	return false unless condition.applyable(table_class)
+      end
+      return true
+    end
+  end

Added: incubator/alois/trunk/rails/app/models/filter_condition.rb
URL: http://svn.apache.org/viewvc/incubator/alois/trunk/rails/app/models/filter_condition.rb?rev=1031127&view=auto
==============================================================================
--- incubator/alois/trunk/rails/app/models/filter_condition.rb (added)
+++ incubator/alois/trunk/rails/app/models/filter_condition.rb Thu Nov  4 18:27:22 2010
@@ -0,0 +1,36 @@
+# Copyright 2010 The Apache Software Foundation.
+# 
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# 
+# http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Condition based on filters.
+# Possible values are a comma separated list of filter_ids.
+class FilterCondition < Condition
+
+  def applyable(options = {})
+    filters.each {|f| 
+      return false unless f.applyable(options)
+    }
+    true
+  end
+
+  def filters
+    f = []
+    value.gsub(/\d+/) {|m| f.push(Filter.find(m))}
+    f
+  end
+   
+  def sql(options = {})    
+    value.gsub(/\d+/) {|m| Filter.find(m).sql(options)}
+  end
+  
+end

Added: incubator/alois/trunk/rails/app/models/generic_record.rb
URL: http://svn.apache.org/viewvc/incubator/alois/trunk/rails/app/models/generic_record.rb?rev=1031127&view=auto
==============================================================================
--- incubator/alois/trunk/rails/app/models/generic_record.rb (added)
+++ incubator/alois/trunk/rails/app/models/generic_record.rb Thu Nov  4 18:27:22 2010
@@ -0,0 +1,89 @@
+# Copyright 2010 The Apache Software Foundation.
+# 
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# 
+# http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+  # This record is used to acces tables and views without models.
+  # TODO: Probably we will get troubels if two tables use this class simultaneously, or two users use alois at the same time. We could on demand create new classes for each table.
+  class GenericRecord < ActiveRecord::Base
+
+    # Returns a class for accessing the table with tablename.
+    def self.get_class_from_tablename(tablename)
+      return nil unless tablename
+      GenericRecord.table_name = tablename
+      self
+    end
+
+    # Returns a sql query for view creation out of UI.
+    def join_query(query = nil)      
+      query = "#{self.class.table_name}" unless query     
+      p = parent
+      if p then
+	query = "#{query} LEFT JOIN #{p.class.table_name} ON #{self.class.table_name}.id = #{p.class.table_name}.id"
+	query = parent.join_query(query)
+      end
+      return query
+    end  
+
+    # returns the view if the table has a view and the field do_not_use_view_for_query is true
+    def self.get_view_if_it_does_not_use_mysql_view
+      view = View.get_class_from_tablename(self.table_name)
+      return nil if view.nil?
+      return nil unless view.do_not_use_view_for_query
+      view
+    end
+
+    # TODO: Document this after view class is documented
+    def self.override_query(options)
+      if view = get_view_if_it_does_not_use_mysql_view
+	View.update_query(view.sql,options)
+      end      
+    end
+
+    # Returns the last executed find query of generic record. Just for debugging purpose
+    def self.last_executed_find
+      @last_sql
+    end
+
+    # Activerecord like finder function, with special behaviour for views
+    def self.find(*args)
+      if args.first == :all and (@last_sql = self.override_query(args[1]))
+	return find_by_sql(@last_sql)
+      end
+      
+      if [:first, :last, :all].include?(args.first)
+	return super
+      end
+
+      id_f = "id"
+      if (view = get_view_if_it_does_not_use_mysql_view)
+	id_f = "#{view.id_source_table}.id" 
+      end
+
+      if @last_sql = self.override_query(:conditions => "#{id_f} = #{args[0].to_i}")
+	find_by_sql(@last_sql)[0]
+      else
+	super
+      end
+    end
+
+    # Activerecord like count function, with special behaviour for views
+    def self.count(*args)
+      if m_query = self.override_query(args[0])
+	self.connection.select_value("SELECT COUNT(*) FROM (#{m_query}) AS m_query").to_i
+      else
+	super
+      end
+    end
+
+  end
+

Added: incubator/alois/trunk/rails/app/models/ip_range.rb
URL: http://svn.apache.org/viewvc/incubator/alois/trunk/rails/app/models/ip_range.rb?rev=1031127&view=auto
==============================================================================
--- incubator/alois/trunk/rails/app/models/ip_range.rb (added)
+++ incubator/alois/trunk/rails/app/models/ip_range.rb Thu Nov  4 18:27:22 2010
@@ -0,0 +1,58 @@
+# Copyright 2010 The Apache Software Foundation.
+# 
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# 
+# http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+class IpRange < ActiveRecord::Base
+
+  validates_presence_of :name, :from_ip
+  
+  # Validates if all IP-fields can be parse.
+  validate do |ir|
+    ir["from_ip"].to_ip rescue ir.errors.add("from_ip",$!)
+    (ir["to_ip"].to_ip rescue ir.errors.add("to_ip",$!)) unless ir.to_ip.blank?
+    (ir["netmask"].to_ip rescue ir.errors.add("netmask",$!)) unless ir.netmask.blank?
+  end
+
+  # Find all ip-ranges that the given IP includes.
+  def self.find_including_range(ip)
+   IpRange.find(:all,:conditions => ["((INET_ATON(from_ip) = INET_ATON(?) AND (to_ip = '' OR to_ip IS NULL)) OR  (INET_ATON(from_ip) <= INET_ATON(?) AND INET_ATON(?) <= INET_ATON(to_ip))) AND enabled = 1", ip.to_s, ip.to_s, ip.to_s],
+		 :order => "INET_ATON(from_ip) DESC,INET_ATON(to_ip) DESC")
+  end
+
+  # Return true if the iprange contans only one single IP.
+  def single_ip?
+    return true if to_ip.blank? and netmask.blank?
+  end
+
+  # Returns from_ip as IpAddress object (if parsing failed, unparesd field is returned)
+  def from_ip
+    return super.to_ip if super
+  rescue
+    super
+  end
+
+  # Returns to_ip as IpAddress object (if parsing failed, unparesd field is returned)
+  def to_ip
+    return super.to_ip if super
+  rescue
+    super
+  end
+
+  # Returns netmask as IpAddress object (if parsing failed, unparesd field is returned)
+  def netmask
+    return super.to_ip if super
+  rescue
+    super
+  end
+
+end

Added: incubator/alois/trunk/rails/app/models/ip_range_condition.rb
URL: http://svn.apache.org/viewvc/incubator/alois/trunk/rails/app/models/ip_range_condition.rb?rev=1031127&view=auto
==============================================================================
--- incubator/alois/trunk/rails/app/models/ip_range_condition.rb (added)
+++ incubator/alois/trunk/rails/app/models/ip_range_condition.rb Thu Nov  4 18:27:22 2010
@@ -0,0 +1,83 @@
+# Copyright 2010 The Apache Software Foundation.
+# 
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# 
+# http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Condition for selecting IPs.
+# possible values:
+# an ip: 192.168.1.1
+# a range: 192.168.1.1-192.168.1.50
+# an id of a range: 3
+# the name of a filter: Filter 1
+class IpRangeCondition < Condition
+  
+  # Find iprange represented by value. Either by id or name.
+  def find_ip_range
+    begin
+      IpRange.find(@value)
+    rescue
+      IpRange.find_by_name(@value)
+    end
+  rescue
+    nil
+  end
+
+  # This condition cannot be updated from UI
+  def updatable?; false; end
+
+  # Returns the range represented by value.
+  def range
+    case @value
+    when /^\s*(\d+\.\d+\.\d+\.\d+)\s*$/
+      $1.to_ip
+    when /^\s*(\d+\.\d+\.\d+\.\d+)\s*(\.\.|\-)\s*(\d+\.\d+\.\d+\.\d+)\s*$/
+      ($1.to_ip..$3.to_ip)
+    else
+      ipr = find_ip_range
+      raise "Cannot find iprange '#{@value}'." unless ipr
+      if ipr.to_ip
+	(ipr.from_ip..ipr.to_ip)
+      else
+	ipr.from_ip
+      end
+    end
+  end
+
+  # Human readable text of the value (probably not really what you expect)
+  def value
+    if ipr = find_ip_range
+      ipr.name + ": " + range.to_s
+    else
+      range.inspect
+    end
+  end
+
+  # Return true if the condition includes only a single IP
+  def exact_ip?
+    range.class.name == "IpAddress"
+  end
+  
+  # SQL condition. MySQL function INET_ATON is used to convert ip name to comparable values but
+  # the column itself must contain "human readable" ipaddresses.
+  # Maybe later implementation will respect column type to compare against integer represented ipaddresses in database.
+  def sql(options = {})
+    ret = ""
+    if exact_ip?
+      ret += "INET_ATON(#{column}) = INET_ATON('#{self.range}') "
+    else
+      ret += "INET_ATON(#{column}) >= INET_ATON('#{self.range.first}') AND "
+      ret += "INET_ATON(#{column}) <= INET_ATON('#{self.range.last}') "
+    end
+    return ret
+  end
+  
+end

Added: incubator/alois/trunk/rails/app/models/mysql_mixin.rb
URL: http://svn.apache.org/viewvc/incubator/alois/trunk/rails/app/models/mysql_mixin.rb?rev=1031127&view=auto
==============================================================================
--- incubator/alois/trunk/rails/app/models/mysql_mixin.rb (added)
+++ incubator/alois/trunk/rails/app/models/mysql_mixin.rb Thu Nov  4 18:27:22 2010
@@ -0,0 +1,70 @@
+# Copyright 2010 The Apache Software Foundation.
+# 
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# 
+# http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This mixin includes all mysql specific
+# functions. Here you can override the
+# class and instance methods.
+# 
+# /!\ This implementation does not work correct yet. /!\
+# TOOD: Finish nonym implementation
+module MysqlMixin
+
+  module ClassMethods
+
+    # returns the current auto_increment value of the table
+    def auto_increment
+      connection.auto_increment(self.table_name)
+    end
+    
+    # Returns table status information of the table
+    def read_db_status(status_name)
+      res = connection.execute("SHOW STATUS")
+      res.each_hash {|h|
+	return h["Value"] if h["Variable_name"].downcase == status_name.downcase
+      }
+      raise "Status with name #{status_name} not found."
+    end
+
+    # "reload" table informations
+    def flush_tables
+      connection.flush_tables
+    end
+
+    # Compute a approximate count of the table. This is useful for
+    # slow innodb counts and computed out of table status information.
+    # If approx count is below 20000, a exact count will be returned.
+    def approx_count
+      return count unless connection.respond_to?(:approx_count)
+      a_count = connection.approx_count(self.table_name)
+      return a_count unless a_count
+      if a_count < 20000
+	count
+      else
+	a_count
+      end
+    end
+
+  end
+  
+  module InstanceMethods
+  end
+  
+  def self::included other
+    other.module_eval  { include InstanceMethods }
+    other.extend ClassMethods
+    
+    other
+  end
+   
+end

Added: incubator/alois/trunk/rails/app/models/nonym.rb
URL: http://svn.apache.org/viewvc/incubator/alois/trunk/rails/app/models/nonym.rb?rev=1031127&view=auto
==============================================================================
--- incubator/alois/trunk/rails/app/models/nonym.rb (added)
+++ incubator/alois/trunk/rails/app/models/nonym.rb Thu Nov  4 18:27:22 2010
@@ -0,0 +1,103 @@
+# Copyright 2010 The Apache Software Foundation.
+# 
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# 
+# http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+  # Model for storing anonymized information ids.
+  # /!\
+  # This implementation is not yet productive and does
+  # not yet work really!!!
+  # /!\
+  # Example: Nonym.anonymize_column(LogMeta)
+  class Nonym < ActiveRecord::Base
+
+#    description "Nonym information"
+
+    # Find or create a new name to be anonymized
+    def self.find_or_create(realname)
+      record = self.find_by_real_name(realname)
+      if not record
+	$log.info("Nonym #{realname} not found - creating.") if $log.info?
+	record = self.new()
+	record.real_name = realname
+	record.save
+      end
+      return record
+    end
+       
+    # Anonymize a whole column. /!\ This change will not be reflected
+    # in schema.rb, so any later migration could fail.
+    def self.anonymize_column(klass, column)
+      $log.debug("Anonymize column #{column} in table #{klass.table_name}.")
+      begin	
+	klass.connection.add_column(klass.table_name, "#{column}_anonym", :integer)
+	klass.connection.add_index(klass.table_name, "#{column}_anonym")
+	total = klass.count()
+	start = DateTime.now
+	curr = 0
+	out = false
+	for record in klass.find(:all)	  
+	  if ((DateTime.now - start).to_i % 5) == 0
+	    $log.info("#{(100/total*curr).to_i}\%") if $log.info? and not out
+	    out = true
+	  else
+	    out = false
+	  end
+	  record.send("#{column}_anonym=", Nonym.find_or_create(record.send(column)).id)
+	  record.save
+	  curr = curr + 1
+	end
+	$log.info("100\%") if $log.info?
+	$log.debug("Done total #{total} in #{(DateTime.now - start).to_i} seconds.") if $log.info?
+      rescue TransactionError
+	raise $!
+      rescue	     
+	connection.remove_column(klass.table_name,"#{column}_anonym")
+	throw $!
+      end
+      connection.remove_column(klass.table_name, "#{column}")
+    end
+    
+    # Revert anonymized column. The column must be without _anonym ending.
+    def self.nonymize_column(klass, column, limit, with_index)
+      $log.debug("Nonymize column #{column} in table #{klass.table_name}.")
+      begin
+	klass.connection.add_column(klass.table_name, "#{column}", :string, :limit => limit)
+	klass.connection.add_index(klass.table_name, "#{column}") if with_index
+	total = klass.count()
+	start = DateTime.now
+	curr = 0
+	out = false
+	for record in klass.find(:all)	  
+	  if ((DateTime.now - start).to_i % 5) == 0
+	    $log.info("#{(100/total*curr).to_i}\%") if $log.info? and not out
+	    out = true
+	  else
+	    out = false
+	  end
+	  nonym = Nonym.find(record.send("#{column}_anonym"))
+	  record.send("#{column}=",nonym.real_name)
+	  record.save
+	  curr = curr + 1
+	end
+	$log.info("100\%") if $log.info?
+	$log.debug("Done total #{total} in #{(DateTime.now - start).to_i} seconds.") if $log.info?
+      rescue TransactionError
+	raise $!
+      rescue	     
+	klass.connection.remove_column(klass.table_name, "#{column}")
+	throw $!
+      end
+      klass.connection.remove_column(klass.table_name, "#{column}_anonym")
+    end
+  end
+

Added: incubator/alois/trunk/rails/app/models/ossim_config.rb
URL: http://svn.apache.org/viewvc/incubator/alois/trunk/rails/app/models/ossim_config.rb?rev=1031127&view=auto
==============================================================================
--- incubator/alois/trunk/rails/app/models/ossim_config.rb (added)
+++ incubator/alois/trunk/rails/app/models/ossim_config.rb Thu Nov  4 18:27:22 2010
@@ -0,0 +1,30 @@
+# Copyright 2010 The Apache Software Foundation.
+# 
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# 
+# http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# /!\ Ossim is not supported currently
+class OssimConfig < ActiveRecord::Base
+  set_table_name "config"
+  set_primary_key "conf"
+
+  def OssimConfig::set_value(conf, value) 
+    c = OssimConfig.find_by_conf(conf)
+    if c == nil then
+      p "SETTING #{conf} NOT FOUND!!!"
+    else
+      c.value = value
+      c.save
+    end
+  end
+  
+end

Added: incubator/alois/trunk/rails/app/models/ossim_plugin.rb
URL: http://svn.apache.org/viewvc/incubator/alois/trunk/rails/app/models/ossim_plugin.rb?rev=1031127&view=auto
==============================================================================
--- incubator/alois/trunk/rails/app/models/ossim_plugin.rb (added)
+++ incubator/alois/trunk/rails/app/models/ossim_plugin.rb Thu Nov  4 18:27:22 2010
@@ -0,0 +1,23 @@
+# Copyright 2010 The Apache Software Foundation.
+# 
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# 
+# http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# /!\ Ossim is not supported currently
+=begin
+  class OssimPlugin < OssimDbBase
+
+    set_inheritance_column "t"
+    set_table_name "plugin"
+    set_primary_key "id"
+  end
+=end

Added: incubator/alois/trunk/rails/app/models/ossim_plugin_sid.rb
URL: http://svn.apache.org/viewvc/incubator/alois/trunk/rails/app/models/ossim_plugin_sid.rb?rev=1031127&view=auto
==============================================================================
--- incubator/alois/trunk/rails/app/models/ossim_plugin_sid.rb (added)
+++ incubator/alois/trunk/rails/app/models/ossim_plugin_sid.rb Thu Nov  4 18:27:22 2010
@@ -0,0 +1,22 @@
+# Copyright 2010 The Apache Software Foundation.
+# 
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# 
+# http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# /!\ Ossim is not supported currently
+=begin
+  class OssimPluginSid < OssimDbBase
+
+    set_table_name "plugin_sid"
+    set_primary_key "plugin_id,sid"
+  end
+=end

Added: incubator/alois/trunk/rails/app/models/random_datasource.rb
URL: http://svn.apache.org/viewvc/incubator/alois/trunk/rails/app/models/random_datasource.rb?rev=1031127&view=auto
==============================================================================
--- incubator/alois/trunk/rails/app/models/random_datasource.rb (added)
+++ incubator/alois/trunk/rails/app/models/random_datasource.rb Thu Nov  4 18:27:22 2010
@@ -0,0 +1,60 @@
+# Copyright 2010 The Apache Software Foundation.
+# 
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# 
+# http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+class RandomDatasource
+  RANDOM_TABLE_NAME = "RANDOM_TABLE"
+
+  class MyColumn
+    attr_accessor :name
+
+    def initialize(name)
+      @name = name
+    end
+        
+  end
+
+  attr_accessor :columns
+
+  def initialize(columns)
+    @columns = columns.map{|n| MyColumn.new(n)}
+  end
+
+  def name
+    RANDOM_TABLE_NAME
+  end
+  def table_name
+    RANDOM_TABLE_NAME
+  end
+
+  def length
+    100
+  end
+
+  def data
+    rows = []
+    100.times {
+      col = {}
+      columns.map{|c| c.name}.each {|name|
+	col[name] = rand(10)
+      }
+      rows.push(col)
+    }
+    rows
+  end
+
+  def table
+    self
+  end
+  
+end

Added: incubator/alois/trunk/rails/app/models/report.rb
URL: http://svn.apache.org/viewvc/incubator/alois/trunk/rails/app/models/report.rb?rev=1031127&view=auto
==============================================================================
--- incubator/alois/trunk/rails/app/models/report.rb (added)
+++ incubator/alois/trunk/rails/app/models/report.rb Thu Nov  4 18:27:22 2010
@@ -0,0 +1,158 @@
+# Copyright 2010 The Apache Software Foundation.
+# 
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# 
+# http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+class Report < ActiveRecord::Base
+  
+  belongs_to :report_template
+  belongs_to :alarm
+  belongs_to :sentinel
+
+
+  def self.archive_path(path, conditions)
+    if conditions[:date]
+      path = path.gsub(/\%d/, conditions[:date].to_s)
+    end
+    if conditions[:time]
+      path = path.gsub(/\%t/, conditions[:time].to_s)
+    end
+    if conditions[:name]
+      path = path.gsub(/\%n/, conditions[:name].to_s)
+    end
+    path
+  end
+
+  def parent
+    alarm or sentinel
+  end
+
+  def self.default_report_template
+    ReportTemplate.new(:title => "Default Sentinel Report", :text => 
+		       "#{self.description}<br><br><<DEFAULTTABLE>>")
+  end
+
+  def self.generate(report_template, generated_by, options = {})
+    rep = Report.new
+    raise "No report_template given." unless report_template
+    raise "No generator given." unless generated_by
+
+    case generated_by.class.to_s
+    when "Sentinel"
+      rep.sentinel = generated_by
+      rep.generated_by = "sentinel"
+    when "Alarm"
+      rep.alarm = generated_by
+      rep.generated_by = "alarm"
+    when "String"
+      rep.generated_by = generated_by
+    else
+      raise "Don't know how to create report (create by: '#{generated_by}' #{generated_by.class})."
+    end
+
+    rep.report_template = report_template
+
+    now = DateTime.now
+    rep.date = now.strftime("%F")
+    rep.time = now.strftime("%T")
+    rep.name = "#{report_template.name or "NONAME"}" + (" for #{options[:datasource].name}" rescue "")
+    p = Pathname.new(archive_path($report_archive_pattern, {
+				    :date => rep.date,
+				    :time => rep.time,
+				    :name => (rep.name or "NONE")}))
+    p.mkpath
+    rep.path = p.to_s
+    rep.create_files(options)
+    rep.save
+    return rep
+  end
+
+  def pathname
+    p = Pathname.new(path)
+  end
+  
+  def write_file(name, text)
+    open("#{pathname + name}","w") {|f| f.write(text) }
+  end
+
+  def load_file(name)
+    open("#{pathname + name}","r") {|f| f.readlines.join }
+  end
+
+  def create_files(options = {})
+    raise "Report already saved. Cannot create files." unless self.new_record?
+
+    rt = report_template
+    if rt
+      rt.set_data_directory(pathname)
+      rt.mode = :real
+
+      options[:parent] = parent
+      rt.render(options)
+      write_file("report_template.yaml", rt.to_yaml)
+    else
+      throw "NO REPORT TEMPL"
+    end
+    
+    write_file("sentinel.yaml", sentinel.to_yaml) if sentinel
+    
+    self.save
+  end
+
+  def objects
+    original_report_template.charts_with_sources +
+    original_report_template.tables_with_sources
+  end
+
+  def original_report_template
+    rt = ReportTemplate.from_yaml(load_file("report_template.yaml"))
+    rt.set_data_directory(path)
+    rt.mode = :archive
+    rt
+  end
+  def original_report_sentinel
+    ReportTemplate.from_yaml(load_file("sentinel.yaml"))
+  end
+
+  def text(options = {})
+    original_report_template.text(options)
+  end
+
+
+  def html_deprecated(options = {})
+    rt = original_report_template
+#    "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">" +
+#      "<html>" +
+#      "<head>" +
+#      "<title>Report #{name} #{date} #{time}</title>" +
+#      "</head>" +
+#      "<body>" +
+      "<h1>#{name}</h1>" +
+      (options[:html_pre_text] or "") +
+      "<h2>Report</h2>" +      
+      (if options[:include_link] then "#{$root_url}reports/show/#{self.id}" end) +
+      rt.text({:absolute_path => true}) +
+      (options[:html_post_text] or "") 
+#      "</body>" +
+#      "</html>"
+  end
+
+  def html
+    rt = original_report_template
+    rt.text({:absolute_path => true})
+  end
+
+  def files(options = {})
+    original_report_template.files
+  end    
+
+end

Added: incubator/alois/trunk/rails/app/models/report_mailer.rb
URL: http://svn.apache.org/viewvc/incubator/alois/trunk/rails/app/models/report_mailer.rb?rev=1031127&view=auto
==============================================================================
--- incubator/alois/trunk/rails/app/models/report_mailer.rb (added)
+++ incubator/alois/trunk/rails/app/models/report_mailer.rb Thu Nov  4 18:27:22 2010
@@ -0,0 +1,59 @@
+# Copyright 2010 The Apache Software Foundation.
+# 
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# 
+# http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+class ReportMailer < BaseMailer
+
+  def simple(recipients, report, options = {})    
+    text(recipients, report, options)
+    part = html(recipients, report, options)
+
+    add_csv(part,report,options) 
+  end
+  
+  def normal(recipients, report, options = {})
+
+    part = text(recipients, report, options)
+    part = html(recipients, report, options.update(:attachments => report.files.uniq))
+    
+    add_csv(part,report,options)
+  end
+
+  def compress(hash)
+    f = Tempfile.new("/tmp")    
+    require "zip/zip"
+    Zip::ZipOutputStream::open(f.path) {
+      |io|
+      hash.each {|key,content|      
+	io.put_next_entry(key)
+	io.write content
+      }
+    }
+    ret = open(f.path).readlines.join
+    f.delete    
+    ret
+  end
+  
+  def add_csv(part,report,options)
+    options[:add_csv] = true if options[:add_csv].nil?
+    return unless options[:add_csv]
+    objs = report.objects
+    data_hash = objs.map {|obj| ["#{obj.class.name}_#{obj.name}.csv", obj.to_csv]}              
+    part.attachment "application/zip" do |a|
+      a.body = compress(data_hash)
+      a.filename = "csv_datas.zip"
+    end
+  end
+
+  
+end

Added: incubator/alois/trunk/rails/app/models/report_template.rb
URL: http://svn.apache.org/viewvc/incubator/alois/trunk/rails/app/models/report_template.rb?rev=1031127&view=auto
==============================================================================
--- incubator/alois/trunk/rails/app/models/report_template.rb (added)
+++ incubator/alois/trunk/rails/app/models/report_template.rb Thu Nov  4 18:27:22 2010
@@ -0,0 +1,218 @@
+# Copyright 2010 The Apache Software Foundation.
+# 
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# 
+# http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+class ReportTemplate < ActiveRecord::Base
+
+  has_and_belongs_to_many :tables, :order => "priority"
+  has_and_belongs_to_many :charts, :order => "priority"
+
+  belongs_to :view
+  has_many :reports
+  has_many :sentinels
+
+  attr_accessor :mode
+  attr_accessor :parent_obj
+
+  # only for validation needed
+  attr_accessor :ds
+
+  validate do |rt|
+    ds = rt.ds
+    rt.charts.each {|c|     
+      rt.errors.add_to_base("Chart '#{c.name}' not applyable to datasource #{ds.to_s}.") unless c.applyable?(ds)
+    }
+    rt.tables.each {|t| 
+      rt.errors.add_to_base("Table '#{t.name}' not applyable to datasource #{ds.to_s}.") unless t.applyable?(ds)
+    }
+  end
+
+  def applyable?(datasource)
+    self.ds = datasource
+    return self.valid?
+  end
+
+
+  def datasource(options = {})
+    return options[:datasource] if options and options[:datasource]
+    return parent_obj.datasource if parent_obj.respond_to?(:datasource) and parent_obj.datasource
+    return view if view      
+    raise "No view and no parent_obj data available for real rendering (parent_obj: #{parent_obj.inspect})." unless mode == :preview
+  end
+  
+  def datasource_depr(options = {})
+    my_view = find_view(options)
+    case mode
+    when :preview
+    when :real
+      return my_view
+    end
+    nil
+  end
+
+  def set_data_directory(dir)
+    @data_directory = dir
+  end
+  def cache_exist?
+    data_directory(false).exist?
+  end
+  def delete_cache  
+    raise "Unexpected cache directory: '#{data_directory}'" if     
+      !(data_directory.realpath.to_s =~ Regexp.new("^#{Regexp.escape((Pathname.new(RAILS_ROOT) + "tmp/reports/").realpath.to_s)}"))
+    d = data_directory
+    d.rmtree if d.exist? and d.directory?
+  end  
+  def data_directory(create = true)
+    return Pathname.new(@data_directory) if @data_directory
+    p = Pathname.new(RAILS_ROOT) + "tmp/reports/#{id}"
+    p.mkpath if create
+    p
+  end
+
+  def render(options = {})
+    self.parent_obj = options[:parent]
+    
+    my_charts(options).each{|ch| ch.render(options) }
+    my_tables(options).each{|tb| tb.render(options) }    
+
+    @count  = data_count(options)
+
+    moptions = options.clone
+    moptions[:relative_path] = data_directory
+
+    open((data_directory + "index.html").to_s,"w") {|f|
+      f.write(text(moptions))
+    }    
+  end
+
+  def charts_with_sources; my_charts; end
+  def my_charts(options = {})
+    case mode
+    when :archive
+      Dir.glob(data_directory + "chart.*/").sort {|d1,d2|
+	# sort with according to the number
+	(d1 =~ /chart\.(\d*)\/$/;$1.to_i) <=> (d2 =~ /chart\.(\d\/*)$/; $1.to_i)
+      }.map {|dir|
+	Chart.load_yaml(dir,nil)
+      }
+    when :preview
+      num = 0
+      charts.map {|chart|
+	dir = (data_directory + "chart.#{num}/").to_s
+	num += 1
+	if File.exist?(dir)
+	  Chart.load_yaml(dir,nil)
+	else
+	  chart.datasource = datasource(options)
+	  chart.set_data_directory(dir)	
+	  chart
+	end
+      }
+    else
+      num = 0
+      charts.map {|chart|
+	chart.datasource = datasource(options)
+	chart.set_data_directory((data_directory + "chart.#{num}").to_s)	
+	num += 1
+	chart
+      }
+    end
+  end
+
+  def tables_with_sources; my_tables; end
+  def my_tables(options = {})
+    case mode
+    when :archive
+      Dir.glob(data_directory + "table.*/").sort {|d1,d2|
+	# sort with according to the number
+	(d1 =~ /table\.(\d*)\/$/;$1.to_i) <=> (d2 =~ /table\.(\d\/*)$/; $1.to_i)
+      }.map {|dir|
+	Table.load_yaml(dir)
+      }
+    else
+      num = 0
+      tables.map {|table|
+	table.datasource = datasource(options)
+	table.set_data_directory((data_directory + "table.#{num}").to_s)
+	num += 1
+	table
+      }      
+    end
+  end
+
+  def files
+    my_charts.map {|ch| ch.png_file_name}
+  end
+
+  def data_count(options = {})
+    return @count if defined?(@count)
+    throw "Count not defined for mode archive." if mode == :archive
+    return "NOT AVAILABLE" if mode == :preview
+
+    ds = self.datasource(options)
+    if ds.respond_to?(:data)
+      ds.data.length
+    else
+      ds.table.count(:conditions => options[:conditions])
+    end
+  end
+
+  def text(options = {})
+    options ||={}
+    return super unless mode
+    t = super.clone
+    num = -1
+    mcharts = my_charts(options)
+    t.gsub!("<<CHART>>") {|match|
+      num += 1
+      options[:chart_number] = num
+      begin
+	chart = mcharts[num]
+	raise "Too few charts defined." unless chart
+	if options[:absolute_path]
+	  options[:link] ||= @external_link
+	end
+	chart.image_map(options) + chart.image_tag(options)
+      rescue
+	"<span style='color:red;'>Cannot insert chart: #{$!}</span>"
+      end
+    }
+
+    num = -1
+    mtables = my_tables(options)
+    t.gsub!("<<TABLE>>") {|m|
+      num += 1
+      begin
+	table = mtables[num]
+	raise "Too few tables defined." unless table
+	table.html
+      rescue
+	"<span style='color:red;'>Cannot insert table: #{$!}</span>"
+      end
+    }
+
+    t.gsub!("<<COUNT>>") { data_count(options)}
+    t.gsub!("<<VIEW>>") { self.datasource(options) and self.datasource(options).name }
+    t.gsub!("<<CONDITIONS>>") { options[:conditions]}
+    t.gsub!("<<CONDITION>>") { options[:conditions] }
+      
+    t
+  end
+
+#  def save
+#    self.version ||= 0
+#    self.version += 1
+#    super
+#  end
+
+end

Added: incubator/alois/trunk/rails/app/models/report_templates_table.rb
URL: http://svn.apache.org/viewvc/incubator/alois/trunk/rails/app/models/report_templates_table.rb?rev=1031127&view=auto
==============================================================================
--- incubator/alois/trunk/rails/app/models/report_templates_table.rb (added)
+++ incubator/alois/trunk/rails/app/models/report_templates_table.rb Thu Nov  4 18:27:22 2010
@@ -0,0 +1,19 @@
+# Copyright 2010 The Apache Software Foundation.
+# 
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# 
+# http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+class ReportTemplatesTable < ActiveRecord::Base
+  # for fixtures :all in test_helper,
+  # otherwiese errors are produced in
+  # test.log
+end



Mime
View raw message